Merge branch 'develop' into feature/entryFields

# Conflicts:
#	VDS.xcodeproj/project.pbxproj

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2022-11-01 16:42:38 -05:00
commit 46eaac262c
55 changed files with 1443 additions and 2507 deletions

90
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,90 @@
stages:
# - test
- download
- build
- deploy
#test:
# stage: test
# script:
# - echo "This job tests something"
# tags:
# - xcode_12_2
download_artifacts:
stage: download
script:
- ./Scripts/download_dependencies.sh
only:
- branches
- develop
tags:
- bash_shell
environment:
name: oneartifactory
url: https://oneartifactoryprod.verizon.com/artifactory
variables:
ARTIFACTORY_URL: https://oneartifactoryprod.verizon.com/artifactory
build_project:
stage: build
script:
- ./Scripts/build_aggregate.sh
only:
- branches
- develop
tags:
- xcode_12_2
deploy_snapshot:
stage: deploy
script:
- cd Scripts && ./upload_vds_frameworks.sh
only:
- branches
- develop
tags:
- bash_shell
environment:
name: oneartifactory
url: https://oneartifactoryprod.verizon.com/artifactory
variables:
ARTIFACTORY_URL: https://oneartifactoryprod.verizon.com/artifactory
#promote_snapshot:
# stage: go live
# # Prevent artifacts from needing to re-download. Everything we need is in Artifactory.
# dependencies: []
# script:
# # Grab the framework version from the xcode project.
# - framework_ver=$(cd RemoteView && agvtool vers -terse)
# - cd Scripts && ./promote_remote_view_frameworks.sh $framework_ver
# only:
# - tags
# tags:
# - bash_shell
# environment:
# name: oneartifactory
# url: https://oneartifactoryprod.verizon.com/artifactory
# variables:
# ARTIFACTORY_URL: https://oneartifactoryprod.verizon.com/artifactory
#
#create_version_tag:
# stage: tag
# when: manual
# # Prevent artifacts from needing to re-download.
# dependencies: []
# script:
# # Grab the framework version from the xcode project and create a tag of the version.
# - framework_ver=$(cd RemoteView && agvtool vers -terse)
# - git tag -a "v${framework_ver}" -m "Version ${framework_ver} created by gitlab-ci Build"
# # Extract the git repo url to ssh version (git@gitlab.verizon.com)
# - ci_push_repo="git@${CI_SERVER_HOST}:${CI_PROJECT_PATH}.git"
# - echo $ci_push_repo
# # Set the remote url for pushing assuming the gitlab runner has SSH access to the repo.
# - git remote set-url --push origin $ci_push_repo
# - git push origin "v${framework_ver}"
# only:
# - develop
# tags:
# - bash_shell

55
Scripts/build_aggregate.sh Executable file
View File

@ -0,0 +1,55 @@
#!/bin/bash -e
unset TOOLCHAINS #Xcode 7.3 BUG FIX http://stackoverflow.com/questions/36184930/xcodebuild-7-3-cant-enable-bitcode
# define variables
C_PROJECT_NAME="VDS"
PHONE_CONFIGURATION="Release"
BUILD_DIR=$(xcodebuild -showBuildSettings -project ./${C_PROJECT_NAME}.xcodeproj | grep -w -o 'BUILD_DIR = .*' | cut -d\ -f3-)
PHONE_ARCHIVE_PATH="${BUILD_DIR}/${PHONE_CONFIGURATION}-iphoneos/${C_PROJECT_NAME}.xcarchive"
SIMULATOR_CONFIGURATION="Debug"
SIMULATOR_ARCHIVE_PATH="${BUILD_DIR}/${SIMULATOR_CONFIGURATION}-iphonesimulator/${C_PROJECT_NAME}.xcarchive"
FRAMEWORKS_DIR=$BUILD_DIR/../../../Frameworks/VDS
UNIVERSAL_OUTPUTFOLDER="${BUILD_DIR}/universal"
sed -i '' "s|path = \.\.\/SharedFrameworks|path = ${FRAMEWORKS_DIR}|g" ./VDS.xcodeproj/project.pbxproj
# Build device archive
xcodebuild archive \
-scheme "${C_PROJECT_NAME}" \
-configuration ${PHONE_CONFIGURATION} \
-archivePath "${PHONE_ARCHIVE_PATH}" \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-quiet \
SKIP_INSTALL=NO \
ONLY_ACTIVE_ARCH=NO \
FRAMEWORK_SEARCH_PATHS="$FRAMEWORKS_DIR" \
ALWAYS_SEARCH_USER_PATHS=true
# Build simulator archive
xcodebuild archive \
-scheme "${C_PROJECT_NAME}" \
-configuration ${SIMULATOR_CONFIGURATION} \
-archivePath "${SIMULATOR_ARCHIVE_PATH}" \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-quiet \
SKIP_INSTALL=NO \
ONLY_ACTIVE_ARCH=NO \
FRAMEWORK_SEARCH_PATHS=$FRAMEWORKS_DIR \
ALWAYS_SEARCH_USER_PATHS=true
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# Remove any existing xc framework.
rm -rf ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.xcframework
# Create the xcframework in the universal output folder.
xcodebuild -create-xcframework \
-framework "${PHONE_ARCHIVE_PATH}/Products/Library/Frameworks/${C_PROJECT_NAME}.framework" \
-framework "${SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${C_PROJECT_NAME}.framework" \
-output ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.xcframework
# Copy the dSYM file as well.
ditto "${PHONE_ARCHIVE_PATH}/dSYMs/${C_PROJECT_NAME}.framework.dSYM" "${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.xcframework.dSYM"

View File

@ -0,0 +1,25 @@
#!/bin/sh -e
# download_dependencies.sh
#
# Downloads all compiled framework flavors in from Artifactory.
#
# Create new aggregate builds
if [ -z $ARTIFACTORY_URL ]; then
ARTIFACTORY_URL="https://oneartifactoryprod.verizon.com/artifactory"
fi
BUILD_DIR=$(xcodebuild -showBuildSettings -project ./VDS.xcodeproj | grep -w -o 'BUILD_DIR = .*' | cut -d\ -f3-)
FRAMEWORKS_DIR=$BUILD_DIR/../../../Frameworks/VDS
if [ ! -d $FRAMEWORKS_DIR ]; then
mkdir -p $FRAMEWORKS_DIR
fi
./Scripts/download_framework.sh $ARTIFACTORY_URL "$FRAMEWORKS_DIR/VDSColorTokens.xcframework" GVJV_VDS_Maven/@vds-tokens/ios/VDSColorTokens.1.0.6.xcframework.zip
./Scripts/download_framework.sh $ARTIFACTORY_URL "$FRAMEWORKS_DIR/VDSFormControlsTokens.xcframework" GVJV_VDS_Maven/@vds-tokens/ios/VDSFormControlsTokens.1.0.7.xcframework.zip
./Scripts/download_framework.sh $ARTIFACTORY_URL "$FRAMEWORKS_DIR/VDSTypographyTokens.xcframework" GVJV_VDS_Maven/@vds-tokens/ios/VDSTypographyTokens.2.0.0.xcframework.zip

86
Scripts/download_framework.sh Executable file
View File

@ -0,0 +1,86 @@
#!/bin/bash -e
# upload_framework.sh
#
# Downloads an iOS framework from Artificatory.
#
# An API key from Artifcatory is required in the api_key.private file before uploading.
#
URL=$1
LOCALPATH="${2}"
REMOTEPATH="${3}"
LOGFILE=$3
LOCALDIR=$(dirname "${LOCALPATH}")
LOCALBASE=$(basename "${LOCALPATH}")
NEWFILEPATH="${LOCALDIR}"/$(basename "${REMOTEPATH}")
VERSIONFILE=$LOCALDIR/Checksums/"${LOCALBASE}".txt
if [ -z $URL ]; then
echo "The artifactory instance url must be specified as the first argument!"
exit 1
fi
#first argument is error message.
exit_with_error () {
echo "Error: $1"
if [ -f "${NEWFILEPATH}" ]; then
rm -rf "${NEWFILEPATH}" 2
fi
exit 1
}
echo "----------------------------------------------------------"
echo "Logs for ${LOCALBASE}"
echo -e "Local Target: ${LOCALPATH}"
echo -e "Remote Source: ${REMOTEPATH}"
if [ -z "$LOCALPATH" ]; then
exit_with_error "Missing local path argument"
fi
if [ -z "$REMOTEPATH" ]; then
exit_with_error "Missing filename path argument"
fi
#get local and remote checksums for comparison
echo -e "Getting checksums..."
echo -e "URL: ${URL}/api/storage/${REMOTEPATH}"
JSON=$(curl --header "X-JFrog-Art-Api: ${ARTIFACTORY_APIKEY}" -X GET "${URL}/api/storage/${REMOTEPATH}")
CHECKSUM=$(echo "$JSON" | python3 -c 'import sys, json; print (json.load(sys.stdin)["checksums"]["sha1"])')
if [[ -z "$CHECKSUM" ]]; then
exit_with_error "No Checksum found in json: ${JSON}"
fi
echo "Remote checksum ${CHECKSUM}"
if [[ -f "${VERSIONFILE}" ]]; then
OLDCHECKSUM=$(cat "${VERSIONFILE}")
else
OLDCHECKSUM="none"
mkdir -p $(dirname ${VERSIONFILE}) && touch "$VERSIONFILE"
fi
echo "Local checksum ${OLDCHECKSUM}"
#get new framework if no original framework, no local checksum, or remote checksum is different from local.
if [ ! -e "${LOCALPATH}" ] || [ -z "$OLDCHECKSUM" ] || [ "$CHECKSUM" != "$OLDCHECKSUM" ]; then
echo "Downloading..."
echo -e "URL: ${URL}/${REMOTEPATH}"
curl --header "X-JFrog-Art-Api: ${ARTIFACTORY_APIKEY}" -f -X GET "$URL/$REMOTEPATH" --output "${NEWFILEPATH}"
if [ $? -eq 0 ] && [ -e "${NEWFILEPATH}" ]; then
echo "Finished Downloading, begin unzip"
unzip -q -o "${NEWFILEPATH}" -d "${LOCALDIR}"
if [ $? -eq 0 ]; then
echo "Finished unzipping, remove zip"
rm -rf "${NEWFILEPATH}"
echo "Writing new checksum to file"
echo "${CHECKSUM}" > "${VERSIONFILE}"
echo "Successfully downloaded and unzipped archive."
else
exit_with_error "Error unzipping"
fi
else
exit_with_error "Failed to download"
fi
else
echo "Successful, No New Version"
fi

86
Scripts/upload_framework.sh Executable file
View File

@ -0,0 +1,86 @@
#!/bin/bash -e
# upload_framework.sh
#
# Uploads an iOS framework to Artificatory given the local path as the first argument and the remote project name in Verizon OneArtifactory for the second argument.
#
# An API key from Artifcatory is required in the api_key.private file before uploading.
#
# The script will replace [VER] in the provided remote path with the version found in the framework bundle.
#
# Copied from Hedden, Kyle Matthew on 3/2/18.
# Copyright © 2018 Verizon. All rights reserved.
URL=$1
LOCALPATH=$2
REMOTEPATH=$3
FRAMEWORKVER=$4
if [ -z $URL ]; then
echo "The artifactory instance url must be specified as the first argument!"
exit 1
fi
echo ">>> UPLOAD START <<<"
echo "Local path: ${LOCALPATH}"
echo "Remote path: ${REMOTEPATH}"
cat "${LOCALPATH}/Info.plist"
LOCALBASE=$(basename "${LOCALPATH}")
LOCALDIR=$(dirname "${LOCALPATH}")
# Replace the [VER] placeholders with the found version.
REMOTEPATH="${REMOTEPATH//\[VER\]/$FRAMEWORKVER}"
echo -e "Resolved path: \t\t${REMOTEPATH}"
if [ -z $ARTIFACTORY_APIKEY ]; then
# Read the API key from a private file.
read -r APIKEY < "api_key.private"
else
APIKEY=$ARTIFACTORY_APIKEY
fi
if [ -z $APIKEY ]; then
read -p "Artifactory API Key:" APIKEY
echo $APIKEY >> api_key.private
fi
echo -e "API Key: \t\t${APIKEY}"
# Zip the framework & DSYM for uploading.
pushd $LOCALDIR
echo -e "---------\nZipping: \t\t${LOCALBASE}.zip"
zip -r -X "${LOCALBASE}.zip" $LOCALBASE
# Generate framework's SHA-1 checksum.
CHECKSUM=$(shasum -a 1 "${LOCALBASE}.zip" | cut -d " " -f 1)
echo -e "SHA-1 Checksum: \t${CHECKSUM}"
if [ -e ${LOCALBASE}.dSYM ]; then
echo -e "---------\nZipping: \t\t${LOCALBASE}.dSYM.zip"
zip -r -X "${LOCALBASE}.dSYM.zip" $LOCALBASE.dSYM
# Generate its SHA-1 checksum for dsym.
DSYM_CHECKSUM=$(shasum -a 1 "${LOCALBASE}.dSYM.zip" | cut -d " " -f 1)
echo -e "SHA-1 Checksum: \t${DSYM_CHECKSUM}"
fi
popd
mv ${LOCALPATH}.zip .
if [ -e ${LOCALPATH}.dSYM.zip ]; then
mv ${LOCALPATH}.dSYM.zip .
fi
# Upload framework to Artifactory.
echo -e "---------\nUploading to: \t\t${URL}/${REMOTEPATH}.zip"
curl --header "X-JFrog-Art-Api: ${APIKEY}" --header "X-Checksum-Sha1: ${CHECKSUM}" -X PUT "${URL}/${REMOTEPATH}.zip" -T "${LOCALBASE}.zip"
# Cleanup.
rm "${LOCALBASE}.zip"
if [ -e ${LOCALBASE}.dSYM.zip ]; then
# Upload dsym Artifactory.
echo -e "---------\nUploading to: \t\t${URL}/${REMOTEPATH}.dSYM.zip"
curl --header "X-JFrog-Art-Api: ${APIKEY}" --header "X-Checksum-Sha1: ${DSYM_CHECKSUM}" -X PUT "${URL}/${REMOTEPATH}.dSYM.zip" -T "${LOCALBASE}.dSYM.zip"
# Cleanup dsym.
rm ${LOCALBASE}.dSYM.zip
fi
echo -e "\n\n<<< UPLOAD COMPLETE >>>\n\n"

View File

@ -0,0 +1,25 @@
#!/bin/sh -e
# upload_core_ui_frameworks.sh
#
# Uploads all compiled framework flavors in MVMCoreUI to Artifactory with the SNAPSHOT classifier. This is to avoid accidently clobbering a release build of a particular version. Remove the classifier in Artificatory to make a release.
#
# Copied from Hedden, Kyle Matthew on 3/2/18.
#
BUILD_SETTINGS=$(xcodebuild -showBuildSettings -project ../VDS.xcodeproj)
FRAMEWORK_VERSION=$(echo "$BUILD_SETTINGS" | grep -w -o 'MARKETING_VERSION = .*' | cut -d\ -f3-)
if [ $(git tag --list | grep "v${FRAMEWORK_VERSION}") ]; then
echo This version tag has already been committed! Aborting!
exit 1
fi
# Create new aggregate builds
if [ -z $ARTIFACTORY_URL ]; then
ARTIFACTORY_URL="https://oneartifactoryprod.verizon.com/artifactory"
fi
# Upload
BUILD_DIR=$(echo "$BUILD_SETTINGS" | grep -w -o 'BUILD_DIR = .*' | cut -d\ -f3-)
./upload_framework.sh $ARTIFACTORY_URL "${BUILD_DIR}/universal/VDS.xcframework" BPHV_MobileFirst_IOS/com/vzw/hss/myverizon/VDS/[VER]/VDS-[VER]-Debug-SNAPSHOT $FRAMEWORK_VERSION

View File

@ -9,18 +9,12 @@
/* Begin PBXBuildFile section */
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; };
5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; };
5FC35BE528D51414004EBEAC /* ButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE428D51413004EBEAC /* ButtonModel.swift */; };
EA1F265D28B944F00033E859 /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F265B28B944F00033E859 /* CollectionView.swift */; };
EA1F265E28B944F00033E859 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F265C28B944F00033E859 /* CollectionViewCell.swift */; };
EA1F266428B945070033E859 /* RadioSwatchGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F266028B945070033E859 /* RadioSwatchGroupModel.swift */; };
EA1F266528B945070033E859 /* RadioSwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F266128B945070033E859 /* RadioSwatch.swift */; };
EA1F266628B945070033E859 /* RadioSwatchGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F266228B945070033E859 /* RadioSwatchGroup.swift */; };
EA1F266728B945070033E859 /* RadioSwatchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F266328B945070033E859 /* RadioSwatchModel.swift */; };
EA336171288B19200071C351 /* VDS.docc in Sources */ = {isa = PBXBuildFile; fileRef = EA336170288B19200071C351 /* VDS.docc */; };
EA336177288B19210071C351 /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA33616C288B19200071C351 /* VDS.framework */; };
EA33617C288B19210071C351 /* VDSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA33617B288B19210071C351 /* VDSTests.swift */; };
EA33617D288B19210071C351 /* VDS.h in Headers */ = {isa = PBXBuildFile; fileRef = EA33616F288B19200071C351 /* VDS.h */; settings = {ATTRIBUTES = (Public, ); }; };
EA3361A2288B1E840071C351 /* ToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361A1288B1E840071C351 /* ToggleModel.swift */; };
EA3361A8288B23300071C351 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361A7288B23300071C351 /* UIColor.swift */; };
EA3361AA288B25E40071C351 /* Disabling.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361A9288B25E40071C351 /* Disabling.swift */; };
EA3361AD288B26190071C351 /* DataTrackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361AC288B26190071C351 /* DataTrackable.swift */; };
@ -28,7 +22,7 @@
EA3361B6288B2A410071C351 /* Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361B5288B2A410071C351 /* Control.swift */; };
EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */; };
EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361BC288B2C760071C351 /* TypeAlias.swift */; };
EA3361BF288B2EA60071C351 /* ModelHandlerable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361BE288B2EA60071C351 /* ModelHandlerable.swift */; };
EA3361BF288B2EA60071C351 /* Handlerable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361BE288B2EA60071C351 /* Handlerable.swift */; };
EA3361C328902D960071C351 /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361C228902D960071C351 /* Toggle.swift */; };
EA3361C5289030FC0071C351 /* Accessable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361C4289030FC0071C351 /* Accessable.swift */; };
EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361C8289054C50071C351 /* Surfaceable.swift */; };
@ -41,45 +35,34 @@
EA3362302891EB4A0071C351 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA33622F2891EB4A0071C351 /* Fonts.swift */; };
EA33623E2892EE950071C351 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA33623D2892EE950071C351 /* UIDevice.swift */; };
EA3362402892EF6C0071C351 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA33623F2892EF6B0071C351 /* Label.swift */; };
EA3362432892EFF20071C351 /* LabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3362422892EFF20071C351 /* LabelModel.swift */; };
EA3362452892F9130071C351 /* Labelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3362442892F9130071C351 /* Labelable.swift */; };
EA33624728931B050071C351 /* Initable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA33624628931B050071C351 /* Initable.swift */; };
EA3C3B4C2894823E000CA526 /* AnyProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3B4B2894823E000CA526 /* AnyProxy.swift */; };
EA4DB18528CA967F00103EE3 /* SelectorGroupHandlerBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */; };
EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */; };
EA4DB30028DCBC9900103EE3 /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB2FF28DCBC9900103EE3 /* BadgeModel.swift */; };
EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB30128DCBCA500103EE3 /* Badge.swift */; };
EA84F6B128B94A2500D67ABC /* CodableColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA84F6B028B94A2500D67ABC /* CodableColor.swift */; };
EA89200228AECF2A006B9984 /* UIButton+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200128AECF2A006B9984 /* UIButton+Publisher.swift */; };
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */; };
EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200528B526D6006B9984 /* CheckboxGroup.swift */; };
EA89200828B526E0006B9984 /* CheckboxGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200728B526E0006B9984 /* CheckboxGroupModel.swift */; };
EA89200D28B530FD006B9984 /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200C28B530FD006B9984 /* RadioBoxModel.swift */; };
EA89200F28B53921006B9984 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200E28B53921006B9984 /* Selectable.swift */; };
EA89201328B568D8006B9984 /* RadioBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201228B568D8006B9984 /* RadioBox.swift */; };
EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */; };
EA89201728B56CFF006B9984 /* RadioBoxGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201628B56CFF006B9984 /* RadioBoxGroupModel.swift */; };
EAA5EEA928EB3ED0003B3210 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEA828EB3ED0003B3210 /* EntryField.swift */; };
EAA5EEAB28EB3ED9003B3210 /* EntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEAA28EB3ED9003B3210 /* EntryFieldModel.swift */; };
EAA5EEB128EB6A5A003B3210 /* TextEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEB028EB6A5A003B3210 /* TextEntryFieldModel.swift */; };
EAA5EEB328EB6A66003B3210 /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEB228EB6A66003B3210 /* TextEntryField.swift */; };
EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEB428ECBFB4003B3210 /* ImageLabelAttribute.swift */; };
EAA5EEB728ECC03A003B3210 /* ToolTipLabelAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEB628ECC03A003B3210 /* ToolTipLabelAttribute.swift */; };
EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAA5EEB828ECD24B003B3210 /* Icons.xcassets */; };
EAA5EEE028F49DB3003B3210 /* Colorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEDF28F49DB3003B3210 /* Colorable.swift */; };
EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = EAA5EEE328F5B855003B3210 /* VerizonNHGDS-Light.otf */; };
EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */; };
EAA5EEEF28F5C908003B3210 /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEEC28F5C908003B3210 /* VDSTypographyTokens.xcframework */; };
EAA5EEF128F5C909003B3210 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */; };
EAA5EEF328F5C909003B3210 /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */; };
EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */; };
EAB1D29E28A5619500DAE764 /* RadioButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */; };
EAB1D2A128A598FE00DAE764 /* UsesAutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2A028A598FE00DAE764 /* UsesAutoLayout.swift */; };
EAB1D2A328A5994800DAE764 /* Debuggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2A228A5994800DAE764 /* Debuggable.swift */; };
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CC28ABE76000DAE764 /* Withable.swift */; };
EAB1D2CF28ABEF2B00DAE764 /* Typography.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */; };
EAB1D2E628AE842000DAE764 /* Publisher+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */; };
EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */; };
EAC925842911C63100091998 /* Colorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEDF28F49DB3003B3210 /* Colorable.swift */; };
EAC9258C2911C9DE00091998 /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925872911C9DE00091998 /* TextEntryField.swift */; };
EAC9258D2911C9DE00091998 /* TextEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925882911C9DE00091998 /* TextEntryFieldModel.swift */; };
EAC9258E2911C9DE00091998 /* EntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9258A2911C9DE00091998 /* EntryFieldModel.swift */; };
EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9258B2911C9DE00091998 /* EntryField.swift */; };
EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */; };
EAF7F0952899861000B287F5 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* Checkbox.swift */; };
EAF7F0962899861000B287F5 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0942899861000B287F5 /* CheckboxModel.swift */; };
EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; };
EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09D289AAEC000B287F5 /* Constants.swift */; };
EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09F289AB7EC00B287F5 /* View.swift */; };
@ -94,12 +77,7 @@
EAF7F0B5289C126F00B287F5 /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0B4289C126F00B287F5 /* UILabel.swift */; };
EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0B6289C12A600B287F5 /* UITapGestureRecognizer.swift */; };
EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */; };
EAF7F0BB289D80ED00B287F5 /* Modelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0BA289D80ED00B287F5 /* Modelable.swift */; };
EAF7F0F8289DB1AC00B287F5 /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAF7F0E8289DB0DA00B287F5 /* VDSFormControlsTokens.xcframework */; };
EAF7F0F9289DB1AC00B287F5 /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA3361F32891D5290071C351 /* VDSTypographyTokens.xcframework */; };
EAF7F0FB289DB1AC00B287F5 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAF7F0E9289DB0DA00B287F5 /* VDSColorTokens.xcframework */; };
EAF7F11728A1475A00B287F5 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F11528A1475A00B287F5 /* RadioButton.swift */; };
EAF7F11828A1475A00B287F5 /* RadioButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F11628A1475A00B287F5 /* RadioButtonModel.swift */; };
EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F13228A2A16500B287F5 /* AttachmentLabelAttributeModel.swift */; };
/* End PBXBuildFile section */
@ -116,21 +94,13 @@
/* Begin PBXFileReference section */
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = "<group>"; };
5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
5FC35BE428D51413004EBEAC /* ButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModel.swift; sourceTree = "<group>"; };
EA1F265B28B944F00033E859 /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
EA1F265C28B944F00033E859 /* CollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = "<group>"; };
EA1F266028B945070033E859 /* RadioSwatchGroupModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioSwatchGroupModel.swift; sourceTree = "<group>"; };
EA1F266128B945070033E859 /* RadioSwatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioSwatch.swift; sourceTree = "<group>"; };
EA1F266228B945070033E859 /* RadioSwatchGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioSwatchGroup.swift; sourceTree = "<group>"; };
EA1F266328B945070033E859 /* RadioSwatchModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioSwatchModel.swift; sourceTree = "<group>"; };
EA33616C288B19200071C351 /* VDS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VDS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EA33616F288B19200071C351 /* VDS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDS.h; sourceTree = "<group>"; };
EA336170288B19200071C351 /* VDS.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = VDS.docc; sourceTree = "<group>"; };
EA336176288B19210071C351 /* VDSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VDSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
EA33617B288B19210071C351 /* VDSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDSTests.swift; sourceTree = "<group>"; };
EA33618E288B1C0C0071C351 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = "/Users/mattbruce/Documents/Projects/iPhone/Frameworks/MVA-JSONCreator/JSONCreator_iOS/../SharedFrameworks/VDSColorTokens.xcframework"; sourceTree = "<absolute>"; };
EA33618F288B1C0C0071C351 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = "/Users/mattbruce/Documents/Projects/iPhone/Frameworks/MVA-JSONCreator/JSONCreator_iOS/../SharedFrameworks/VDSFormControlsTokens.xcframework"; sourceTree = "<absolute>"; };
EA3361A1288B1E840071C351 /* ToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleModel.swift; sourceTree = "<group>"; };
EA3361A7288B23300071C351 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
EA3361A9288B25E40071C351 /* Disabling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disabling.swift; sourceTree = "<group>"; };
EA3361AC288B26190071C351 /* DataTrackable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTrackable.swift; sourceTree = "<group>"; };
@ -138,11 +108,10 @@
EA3361B5288B2A410071C351 /* Control.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Control.swift; sourceTree = "<group>"; };
EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewProtocol.swift; sourceTree = "<group>"; };
EA3361BC288B2C760071C351 /* TypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeAlias.swift; sourceTree = "<group>"; };
EA3361BE288B2EA60071C351 /* ModelHandlerable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelHandlerable.swift; sourceTree = "<group>"; };
EA3361BE288B2EA60071C351 /* Handlerable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Handlerable.swift; sourceTree = "<group>"; };
EA3361C228902D960071C351 /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = "<group>"; };
EA3361C4289030FC0071C351 /* Accessable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessable.swift; sourceTree = "<group>"; };
EA3361C8289054C50071C351 /* Surfaceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Surfaceable.swift; sourceTree = "<group>"; };
EA3361F32891D5290071C351 /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = "<group>"; };
EA3362002891E14C0071C351 /* VerizonNHGeTX-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Bold.otf"; sourceTree = "<group>"; };
EA3362012891E14D0071C351 /* VerizonNHGeDS-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeDS-Bold.otf"; sourceTree = "<group>"; };
EA3362022891E14D0071C351 /* VerizonNHGeTX-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Regular.otf"; sourceTree = "<group>"; };
@ -152,45 +121,34 @@
EA33622F2891EB4A0071C351 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = "<group>"; };
EA33623D2892EE950071C351 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = "<group>"; };
EA33623F2892EF6B0071C351 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = "<group>"; };
EA3362422892EFF20071C351 /* LabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelModel.swift; sourceTree = "<group>"; };
EA3362442892F9130071C351 /* Labelable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Labelable.swift; sourceTree = "<group>"; };
EA33624628931B050071C351 /* Initable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Initable.swift; sourceTree = "<group>"; };
EA3C3B4B2894823E000CA526 /* AnyProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyProxy.swift; sourceTree = "<group>"; };
EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupHandlerBase.swift; sourceTree = "<group>"; };
EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEquatable.swift; sourceTree = "<group>"; };
EA4DB2FF28DCBC9900103EE3 /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = "<group>"; };
EA4DB30128DCBCA500103EE3 /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = "<group>"; };
EA84F6B028B94A2500D67ABC /* CodableColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableColor.swift; sourceTree = "<group>"; };
EA89200128AECF2A006B9984 /* UIButton+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Publisher.swift"; sourceTree = "<group>"; };
EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Publisher.swift"; sourceTree = "<group>"; };
EA89200528B526D6006B9984 /* CheckboxGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxGroup.swift; sourceTree = "<group>"; };
EA89200728B526E0006B9984 /* CheckboxGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxGroupModel.swift; sourceTree = "<group>"; };
EA89200C28B530FD006B9984 /* RadioBoxModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = "<group>"; };
EA89200E28B53921006B9984 /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = "<group>"; };
EA89201228B568D8006B9984 /* RadioBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBox.swift; sourceTree = "<group>"; };
EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxGroup.swift; sourceTree = "<group>"; };
EA89201628B56CFF006B9984 /* RadioBoxGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxGroupModel.swift; sourceTree = "<group>"; };
EAA5EEA828EB3ED0003B3210 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
EAA5EEAA28EB3ED9003B3210 /* EntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldModel.swift; sourceTree = "<group>"; };
EAA5EEB028EB6A5A003B3210 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = "<group>"; };
EAA5EEB228EB6A66003B3210 /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = "<group>"; };
EAA5EEB428ECBFB4003B3210 /* ImageLabelAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLabelAttribute.swift; sourceTree = "<group>"; };
EAA5EEB628ECC03A003B3210 /* ToolTipLabelAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolTipLabelAttribute.swift; sourceTree = "<group>"; };
EAA5EEB828ECD24B003B3210 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = "<group>"; };
EAA5EEDF28F49DB3003B3210 /* Colorable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colorable.swift; sourceTree = "<group>"; };
EAA5EEE328F5B855003B3210 /* VerizonNHGDS-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGDS-Light.otf"; sourceTree = "<group>"; };
EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupModelable.swift; sourceTree = "<group>"; };
EAA5EEEC28F5C908003B3210 /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = "<group>"; };
EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = "<group>"; };
EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroup.swift; sourceTree = "<group>"; };
EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroupModel.swift; sourceTree = "<group>"; };
EAB1D2A028A598FE00DAE764 /* UsesAutoLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsesAutoLayout.swift; sourceTree = "<group>"; };
EAB1D2A228A5994800DAE764 /* Debuggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debuggable.swift; sourceTree = "<group>"; };
EAB1D2CC28ABE76000DAE764 /* Withable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Withable.swift; sourceTree = "<group>"; };
EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typography.swift; sourceTree = "<group>"; };
EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Publisher+Bind.swift"; sourceTree = "<group>"; };
EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControlPublisher.swift; sourceTree = "<group>"; };
EAC925872911C9DE00091998 /* TextEntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = "<group>"; };
EAC925882911C9DE00091998 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = "<group>"; };
EAC9258A2911C9DE00091998 /* EntryFieldModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryFieldModel.swift; sourceTree = "<group>"; };
EAC9258B2911C9DE00091998 /* EntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Publisher.swift"; sourceTree = "<group>"; };
EAF7F0932899861000B287F5 /* Checkbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
EAF7F0942899861000B287F5 /* CheckboxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxModel.swift; sourceTree = "<group>"; };
EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = "<group>"; };
EAF7F09D289AAEC000B287F5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
@ -205,13 +163,7 @@
EAF7F0B4289C126F00B287F5 /* UILabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILabel.swift; sourceTree = "<group>"; };
EAF7F0B6289C12A600B287F5 /* UITapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITapGestureRecognizer.swift; sourceTree = "<group>"; };
EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorConfiguration.swift; sourceTree = "<group>"; };
EAF7F0BA289D80ED00B287F5 /* Modelable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modelable.swift; sourceTree = "<group>"; };
EAF7F0E8289DB0DA00B287F5 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = "<group>"; };
EAF7F0E9289DB0DA00B287F5 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
EAF7F0EA289DB0DA00B287F5 /* VDSAccessibilityTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSAccessibilityTokens.xcframework; path = ../SharedFrameworks/VDSAccessibilityTokens.xcframework; sourceTree = "<group>"; };
EAF7F0EB289DB0DA00B287F5 /* VDSLayoutTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSLayoutTokens.xcframework; path = ../SharedFrameworks/VDSLayoutTokens.xcframework; sourceTree = "<group>"; };
EAF7F11528A1475A00B287F5 /* RadioButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
EAF7F11628A1475A00B287F5 /* RadioButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButtonModel.swift; sourceTree = "<group>"; };
EAF7F13228A2A16500B287F5 /* AttachmentLabelAttributeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentLabelAttributeModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -220,9 +172,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EAF7F0F8289DB1AC00B287F5 /* VDSFormControlsTokens.xcframework in Frameworks */,
EAF7F0F9289DB1AC00B287F5 /* VDSTypographyTokens.xcframework in Frameworks */,
EAF7F0FB289DB1AC00B287F5 /* VDSColorTokens.xcframework in Frameworks */,
EAA5EEF328F5C909003B3210 /* VDSFormControlsTokens.xcframework in Frameworks */,
EAA5EEEF28F5C908003B3210 /* VDSTypographyTokens.xcframework in Frameworks */,
EAA5EEF128F5C909003B3210 /* VDSColorTokens.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -241,7 +193,6 @@
isa = PBXGroup;
children = (
5FC35BE228D51405004EBEAC /* Button.swift */,
5FC35BE428D51413004EBEAC /* ButtonModel.swift */,
);
path = Button;
sourceTree = "<group>";
@ -250,9 +201,7 @@
isa = PBXGroup;
children = (
EA1F266128B945070033E859 /* RadioSwatch.swift */,
EA1F266328B945070033E859 /* RadioSwatchModel.swift */,
EA1F266228B945070033E859 /* RadioSwatchGroup.swift */,
EA1F266028B945070033E859 /* RadioSwatchGroupModel.swift */,
);
path = RadioSwatch;
sourceTree = "<group>";
@ -284,7 +233,6 @@
EA3361A6288B23240071C351 /* Extensions */,
EA3361DF2891D0F10071C351 /* Fonts */,
EA3361AB288B25EC0071C351 /* Protocols */,
EAB1D29F28A598D000DAE764 /* PropertyWrappers */,
EAB1D2E228AE842000DAE764 /* Publishers */,
EAB1D2D028ABEF3100DAE764 /* Typography */,
EA3361B9288B2BE30071C351 /* Utilities */,
@ -306,13 +254,9 @@
EA33618D288B1C0C0071C351 /* Frameworks */ = {
isa = PBXGroup;
children = (
EAF7F0EA289DB0DA00B287F5 /* VDSAccessibilityTokens.xcframework */,
EAF7F0E9289DB0DA00B287F5 /* VDSColorTokens.xcframework */,
EAF7F0E8289DB0DA00B287F5 /* VDSFormControlsTokens.xcframework */,
EAF7F0EB289DB0DA00B287F5 /* VDSLayoutTokens.xcframework */,
EA3361F32891D5290071C351 /* VDSTypographyTokens.xcframework */,
EA33618E288B1C0C0071C351 /* VDSColorTokens.xcframework */,
EA33618F288B1C0C0071C351 /* VDSFormControlsTokens.xcframework */,
EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */,
EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */,
EAA5EEEC28F5C908003B3210 /* VDSTypographyTokens.xcframework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -327,7 +271,7 @@
EA89200B28B530F0006B9984 /* RadioBox */,
EAF7F11428A1470D00B287F5 /* RadioButton */,
EA1F265F28B945070033E859 /* RadioSwatch */,
EAA5EEA728EB3EBC003B3210 /* TextFields */,
EAC925852911C9DE00091998 /* TextFields */,
EA3361A0288B1E6F0071C351 /* Toggle */,
);
path = Components;
@ -337,7 +281,6 @@
isa = PBXGroup;
children = (
EA3361C228902D960071C351 /* Toggle.swift */,
EA3361A1288B1E840071C351 /* ToggleModel.swift */,
);
path = Toggle;
sourceTree = "<group>";
@ -365,13 +308,9 @@
EA3361A9288B25E40071C351 /* Disabling.swift */,
EAF7F0A1289AFB3900B287F5 /* Errorable.swift */,
EA3361AE288B26310071C351 /* FormFieldable.swift */,
EA3361BE288B2EA60071C351 /* Handlerable.swift */,
EA33624628931B050071C351 /* Initable.swift */,
EA3362442892F9130071C351 /* Labelable.swift */,
EAF7F0BA289D80ED00B287F5 /* Modelable.swift */,
EA3361BE288B2EA60071C351 /* ModelHandlerable.swift */,
EAF7F0A5289B0CE000B287F5 /* Resetable.swift */,
EA89200E28B53921006B9984 /* Selectable.swift */,
EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */,
EA3361C8289054C50071C351 /* Surfaceable.swift */,
EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */,
EAB1D2CC28ABE76000DAE764 /* Withable.swift */,
@ -383,8 +322,6 @@
EA3361B4288B2A360071C351 /* Classes */ = {
isa = PBXGroup;
children = (
EA1F265B28B944F00033E859 /* CollectionView.swift */,
EA1F265C28B944F00033E859 /* CollectionViewCell.swift */,
EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */,
EAF7F09D289AAEC000B287F5 /* Constants.swift */,
EA3361B5288B2A410071C351 /* Control.swift */,
@ -436,7 +373,6 @@
isa = PBXGroup;
children = (
EA33623F2892EF6B0071C351 /* Label.swift */,
EA3362422892EFF20071C351 /* LabelModel.swift */,
EAF7F0A9289B13EF00B287F5 /* Attributes */,
);
path = Label;
@ -446,7 +382,6 @@
isa = PBXGroup;
children = (
EA4DB30128DCBCA500103EE3 /* Badge.swift */,
EA4DB2FF28DCBC9900103EE3 /* BadgeModel.swift */,
);
path = Badge;
sourceTree = "<group>";
@ -455,51 +390,11 @@
isa = PBXGroup;
children = (
EA89201228B568D8006B9984 /* RadioBox.swift */,
EA89200C28B530FD006B9984 /* RadioBoxModel.swift */,
EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */,
EA89201628B56CFF006B9984 /* RadioBoxGroupModel.swift */,
);
path = RadioBox;
sourceTree = "<group>";
};
EAA5EEA728EB3EBC003B3210 /* TextFields */ = {
isa = PBXGroup;
children = (
EAA5EEAF28EB6A49003B3210 /* TextEntryField */,
EAA5EEAE28EB6A3D003B3210 /* EntryField */,
);
path = TextFields;
sourceTree = "<group>";
};
EAA5EEAE28EB6A3D003B3210 /* EntryField */ = {
isa = PBXGroup;
children = (
EAA5EEA828EB3ED0003B3210 /* EntryField.swift */,
EAA5EEAA28EB3ED9003B3210 /* EntryFieldModel.swift */,
);
path = EntryField;
sourceTree = "<group>";
};
EAA5EEAF28EB6A49003B3210 /* TextEntryField */ = {
isa = PBXGroup;
children = (
EAA5EEB228EB6A66003B3210 /* TextEntryField.swift */,
EAA5EEB028EB6A5A003B3210 /* TextEntryFieldModel.swift */,
);
path = TextEntryField;
sourceTree = "<group>";
};
EAB1D29F28A598D000DAE764 /* PropertyWrappers */ = {
isa = PBXGroup;
children = (
EA3C3B4B2894823E000CA526 /* AnyProxy.swift */,
EA84F6B028B94A2500D67ABC /* CodableColor.swift */,
EAB1D2A228A5994800DAE764 /* Debuggable.swift */,
EAB1D2A028A598FE00DAE764 /* UsesAutoLayout.swift */,
);
path = PropertyWrappers;
sourceTree = "<group>";
};
EAB1D2D028ABEF3100DAE764 /* Typography */ = {
isa = PBXGroup;
children = (
@ -520,13 +415,38 @@
path = Publishers;
sourceTree = "<group>";
};
EAC925852911C9DE00091998 /* TextFields */ = {
isa = PBXGroup;
children = (
EAC925862911C9DE00091998 /* TextEntryField */,
EAC925892911C9DE00091998 /* EntryField */,
);
path = TextFields;
sourceTree = "<group>";
};
EAC925862911C9DE00091998 /* TextEntryField */ = {
isa = PBXGroup;
children = (
EAC925872911C9DE00091998 /* TextEntryField.swift */,
EAC925882911C9DE00091998 /* TextEntryFieldModel.swift */,
);
path = TextEntryField;
sourceTree = "<group>";
};
EAC925892911C9DE00091998 /* EntryField */ = {
isa = PBXGroup;
children = (
EAC9258A2911C9DE00091998 /* EntryFieldModel.swift */,
EAC9258B2911C9DE00091998 /* EntryField.swift */,
);
path = EntryField;
sourceTree = "<group>";
};
EAF7F092289985E200B287F5 /* Checkbox */ = {
isa = PBXGroup;
children = (
EAF7F0932899861000B287F5 /* Checkbox.swift */,
EAF7F0942899861000B287F5 /* CheckboxModel.swift */,
EA89200528B526D6006B9984 /* CheckboxGroup.swift */,
EA89200728B526E0006B9984 /* CheckboxGroupModel.swift */,
);
path = Checkbox;
sourceTree = "<group>";
@ -539,10 +459,10 @@
EAF7F13228A2A16500B287F5 /* AttachmentLabelAttributeModel.swift */,
EAF7F0B0289B177F00B287F5 /* ColorLabelAttribute.swift */,
EAF7F0AA289B13FD00B287F5 /* FontLabelAttribute.swift */,
EAF7F0AC289B142900B287F5 /* StrikeThroughLabelAttribute.swift */,
EAA5EEB428ECBFB4003B3210 /* ImageLabelAttribute.swift */,
EAF7F0AE289B144C00B287F5 /* UnderlineLabelAttribute.swift */,
EAF7F0AC289B142900B287F5 /* StrikeThroughLabelAttribute.swift */,
EAA5EEB628ECC03A003B3210 /* ToolTipLabelAttribute.swift */,
EAF7F0AE289B144C00B287F5 /* UnderlineLabelAttribute.swift */,
);
path = Attributes;
sourceTree = "<group>";
@ -551,9 +471,7 @@
isa = PBXGroup;
children = (
EAF7F11528A1475A00B287F5 /* RadioButton.swift */,
EAF7F11628A1475A00B287F5 /* RadioButtonModel.swift */,
EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */,
EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */,
);
path = RadioButton;
sourceTree = "<group>";
@ -678,36 +596,27 @@
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */,
EA3361C328902D960071C351 /* Toggle.swift in Sources */,
EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */,
EAF7F11828A1475A00B287F5 /* RadioButtonModel.swift in Sources */,
EA89200D28B530FD006B9984 /* RadioBoxModel.swift in Sources */,
EA1F266728B945070033E859 /* RadioSwatchModel.swift in Sources */,
EAA5EEB328EB6A66003B3210 /* TextEntryField.swift in Sources */,
EA89201328B568D8006B9984 /* RadioBox.swift in Sources */,
EA84F6B128B94A2500D67ABC /* CodableColor.swift in Sources */,
EAA5EEE028F49DB3003B3210 /* Colorable.swift in Sources */,
EAC9258C2911C9DE00091998 /* TextEntryField.swift in Sources */,
EA3362402892EF6C0071C351 /* Label.swift in Sources */,
EAC9258D2911C9DE00091998 /* TextEntryFieldModel.swift in Sources */,
EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */,
EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */,
EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */,
EAA5EEB728ECC03A003B3210 /* ToolTipLabelAttribute.swift in Sources */,
EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */,
EAC925842911C63100091998 /* Colorable.swift in Sources */,
EA3361C5289030FC0071C351 /* Accessable.swift in Sources */,
EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */,
EAA5EEAB28EB3ED9003B3210 /* EntryFieldModel.swift in Sources */,
EAA5EEA928EB3ED0003B3210 /* EntryField.swift in Sources */,
EAF7F11728A1475A00B287F5 /* RadioButton.swift in Sources */,
EAB1D2A128A598FE00DAE764 /* UsesAutoLayout.swift in Sources */,
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */,
EAF7F0952899861000B287F5 /* Checkbox.swift in Sources */,
EA89201728B56CFF006B9984 /* RadioBoxGroupModel.swift in Sources */,
EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */,
EA3361A2288B1E840071C351 /* ToggleModel.swift in Sources */,
EA1F265D28B944F00033E859 /* CollectionView.swift in Sources */,
EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */,
EA3362432892EFF20071C351 /* LabelModel.swift in Sources */,
EA33624728931B050071C351 /* Initable.swift in Sources */,
EAF7F0A4289B017C00B287F5 /* LabelAttributeModel.swift in Sources */,
EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */,
EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */,
EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */,
EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */,
EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */,
@ -716,44 +625,30 @@
EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */,
EAB1D2CF28ABEF2B00DAE764 /* Typography.swift in Sources */,
EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */,
EA89200F28B53921006B9984 /* Selectable.swift in Sources */,
EAB1D29E28A5619500DAE764 /* RadioButtonGroupModel.swift in Sources */,
EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */,
EA3C3B4C2894823E000CA526 /* AnyProxy.swift in Sources */,
5FC35BE528D51414004EBEAC /* ButtonModel.swift in Sources */,
EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */,
EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */,
EAF7F0BB289D80ED00B287F5 /* Modelable.swift in Sources */,
EAA5EEB128EB6A5A003B3210 /* TextEntryFieldModel.swift in Sources */,
EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */,
EA4DB30028DCBC9900103EE3 /* BadgeModel.swift in Sources */,
EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */,
EA1F266528B945070033E859 /* RadioSwatch.swift in Sources */,
EA4DB18528CA967F00103EE3 /* SelectorGroupHandlerBase.swift in Sources */,
EAC9258E2911C9DE00091998 /* EntryFieldModel.swift in Sources */,
EA89200228AECF2A006B9984 /* UIButton+Publisher.swift in Sources */,
EAF7F0AB289B13FD00B287F5 /* FontLabelAttribute.swift in Sources */,
EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */,
EA336171288B19200071C351 /* VDS.docc in Sources */,
EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */,
EAF7F0962899861000B287F5 /* CheckboxModel.swift in Sources */,
EAB1D2E628AE842000DAE764 /* Publisher+Bind.swift in Sources */,
EA3361AA288B25E40071C351 /* Disabling.swift in Sources */,
EA89200828B526E0006B9984 /* CheckboxGroupModel.swift in Sources */,
EA3361B6288B2A410071C351 /* Control.swift in Sources */,
EAB1D2A328A5994800DAE764 /* Debuggable.swift in Sources */,
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */,
EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */,
EA3362452892F9130071C351 /* Labelable.swift in Sources */,
EA3361AD288B26190071C351 /* DataTrackable.swift in Sources */,
EAA5EEE228F4C62C003B3210 /* Colorable.swift in Sources */,
EA33623E2892EE950071C351 /* UIDevice.swift in Sources */,
EA3362302891EB4A0071C351 /* Fonts.swift in Sources */,
EA1F265E28B944F00033E859 /* CollectionViewCell.swift in Sources */,
EAF7F0AD289B142900B287F5 /* StrikeThroughLabelAttribute.swift in Sources */,
EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */,
EA3361BF288B2EA60071C351 /* ModelHandlerable.swift in Sources */,
EA3361BF288B2EA60071C351 /* Handlerable.swift in Sources */,
EA3361A8288B23300071C351 /* UIColor.swift in Sources */,
EA1F266428B945070033E859 /* RadioSwatchGroupModel.swift in Sources */,
EA1F266628B945070033E859 /* RadioSwatchGroup.swift in Sources */,
5FC35BE328D51405004EBEAC /* Button.swift in Sources */,
);
@ -901,6 +796,7 @@
EA336181288B19210071C351 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@ -908,6 +804,7 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@ -923,7 +820,7 @@
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -932,6 +829,7 @@
EA336182288B19210071C351 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@ -939,6 +837,7 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@ -954,7 +853,7 @@
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};

View File

@ -1,110 +0,0 @@
//
// CollectionView.swift
// VDS
//
// Created by Matt Bruce on 8/25/22.
//
import Foundation
import UIKit
import Combine
open class CollectionView<ModelType: Modelable>: UICollectionView, ModelHandlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
private var initialSetupPerformed = false
@Proxy(\.model.surface)
open var surface: Surface
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var isEnabled: Bool {
get { !model.disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
}
}
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
initialSetup()
set(with: model)
}
public required init(with model: ModelType, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: .zero, collectionViewLayout: layout)
initialSetup()
set(with: model)
}
public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
initialSetup()
set(with: model)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
public func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setupUpdateView()
setup()
}
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
fatalError("Implement updateView")
}
open func reset() {
backgroundColor = .clear
if let model = model as? Resettable {
model.reset()
}
}
// MARK: - ViewProtocol
/// Will be called only once.
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
}
}

View File

@ -1,97 +0,0 @@
//
// CollectionViewCell.swift
// VDS
//
// Created by Matt Bruce on 8/25/22.
//
import Foundation
import UIKit
import Combine
open class CollectionViewCell<ModelHandlerType: ModelHandlerable & UIView>: UICollectionViewCell, ViewProtocol {
public typealias ModelType = ModelHandlerType.ModelType
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
private var initialSetupPerformed = false
public var modelHandler: ModelHandlerType = ModelHandlerType()
@Proxy(\.modelHandler.model.surface)
open var surface: Surface
@Proxy(\.modelHandler.model.disabled)
open var disabled: Bool
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: frame)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
public func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setup()
}
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func shouldUpdateView(viewModel: ModelType) -> Bool {
return modelHandler.shouldUpdateView(viewModel: viewModel)
}
open func updateView(viewModel: ModelType) {
modelHandler.updateView(viewModel: viewModel)
}
public func set(with model: ModelType) {
modelHandler.set(with: model)
}
// MARK: - ViewProtocol
/// Will be called only once.
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
addSubview(modelHandler)
modelHandler.topAnchor.constraint(equalTo: topAnchor).isActive = true
modelHandler.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
modelHandler.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
modelHandler.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
open override func prepareForReuse() {
super.prepareForReuse()
if let handler = modelHandler as? Resettable {
handler.reset()
}
}
}

View File

@ -23,13 +23,13 @@ public typealias ObjectColorable = Colorable & Initable & ObjectWithable
///
/// let textColor = config.getColor(model) //returns .black
final public class SurfaceColorConfiguration: ObjectColorable {
public typealias ModelType = Surfaceable
public typealias ObjectType = Surfaceable
public var lightColor: UIColor = .clear
public var darkColor: UIColor = .clear
required public init(){}
public func getColor(_ viewModel: any ModelType) -> UIColor {
return viewModel.surface == .light ? lightColor : darkColor
public func getColor(_ object: any ObjectType) -> UIColor {
return object.surface == .light ? lightColor : darkColor
}
}
@ -42,8 +42,8 @@ public protocol DisabledSurfaceColorable: ObjectColorable {
}
extension DisabledSurfaceColorable {
public func getDisabledColor<M: Surfaceable & Disabling> (_ viewModel: M) -> UIColor {
viewModel.disabled ? disabled.getColor(viewModel) : enabled.getColor(viewModel)
public func getDisabledColor<M: Surfaceable & Disabling> (_ object: M) -> UIColor {
object.disabled ? disabled.getColor(object) : enabled.getColor(object)
}
}
@ -69,14 +69,14 @@ extension DisabledSurfaceColorable {
///
///
final public class DisabledSurfaceColorConfiguration: DisabledSurfaceColorable {
public typealias ModelType = Surfaceable & Disabling
public typealias ObjectType = Surfaceable & Disabling
public var disabled = SurfaceColorConfiguration()
public var enabled = SurfaceColorConfiguration()
required public init(){}
public func getColor(_ viewModel: any ModelType) -> UIColor {
getDisabledColor(viewModel)
public func getColor(_ object: any ObjectType) -> UIColor {
getDisabledColor(object)
}
}
@ -84,11 +84,11 @@ final public class DisabledSurfaceColorConfiguration: DisabledSurfaceColorable {
///MARK -- BinaryColorable
///-------------------------------------------------------------------
public protocol BinaryColorable{
var userTrueColor: Bool { get }
var useTrueColor: Bool { get }
}
extension BinaryColorable where Self: Selectable {
public var userTrueColor: Bool { return selected }
extension BinaryColorable where Self: Control {
public var useTrueColor: Bool { return isSelected }
}
///-------------------------------------------------------------------
@ -100,8 +100,8 @@ public protocol BinarySurfaceColorable: ObjectColorable {
}
extension BinarySurfaceColorable {
public func getBinaryColor<M: Surfaceable & BinaryColorable>(_ viewModel: M) -> UIColor {
viewModel.userTrueColor ? forTrue.getColor(viewModel) : forFalse.getColor(viewModel)
public func getBinaryColor<M: Surfaceable & BinaryColorable>(_ object: M) -> UIColor {
object.useTrueColor ? forTrue.getColor(object) : forFalse.getColor(object)
}
}
@ -126,14 +126,14 @@ extension BinarySurfaceColorable {
///
///
final public class BinarySurfaceColorConfiguration: BinarySurfaceColorable {
public typealias ModelType = Surfaceable & BinaryColorable
public typealias ObjectType = Surfaceable & BinaryColorable
public var forTrue = SurfaceColorConfiguration()
public var forFalse = SurfaceColorConfiguration()
required public init(){}
public func getColor(_ viewModel: any ModelType) -> UIColor {
getBinaryColor(viewModel)
public func getColor(_ object: any ObjectType) -> UIColor {
getBinaryColor(object)
}
}
@ -147,8 +147,8 @@ public protocol BinaryDisabledSurfaceColorable: ObjectColorable {
}
extension BinaryDisabledSurfaceColorable {
public func getBinaryColor<M: Disabling & Surfaceable & BinaryColorable>(_ viewModel: M) -> UIColor {
viewModel.userTrueColor ? forTrue.getColor(viewModel) : forFalse.getColor(viewModel)
public func getBinaryColor<M: Disabling & Surfaceable & BinaryColorable>(_ object: M) -> UIColor {
object.useTrueColor ? forTrue.getColor(object) : forFalse.getColor(object)
}
}
@ -179,13 +179,13 @@ extension BinaryDisabledSurfaceColorable {
///
///
final public class BinaryDisabledSurfaceColorConfiguration: BinaryDisabledSurfaceColorable {
public typealias ModelType = Disabling & Surfaceable & BinaryColorable
public typealias ObjectType = Disabling & Surfaceable & BinaryColorable
public var forTrue = DisabledSurfaceColorConfiguration()
public var forFalse = DisabledSurfaceColorConfiguration()
required public init(){}
public func getColor(_ viewModel: any ModelType) -> UIColor {
getBinaryColor(viewModel)
public func getColor(_ object: any ObjectType) -> UIColor {
getBinaryColor(object)
}
}

View File

@ -8,6 +8,6 @@
import Foundation
public struct Constants {
public static let ModelStateDebounce = 0.02
public static let StateDebounce = 0.0 //.001
public static let PaddingOne = 10.0
}

View File

@ -9,44 +9,34 @@ import Foundation
import UIKit
import Combine
open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProtocol, Resettable {
@objc(VDSControl)
open class Control: UIControl, Handlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
private var initialSetupPerformed = false
open var surface: Surface = .light { didSet { didChange() } }
//if set to true this will call the
//defaultAction() in the class
public var executeDefaultAction = true
@Proxy(\.model.surface)
open var surface: Surface
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open override var isSelected: Bool { didSet { didChange() } }
open override var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
didChange()
}
}
@ -58,12 +48,6 @@ open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProto
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
@ -78,10 +62,9 @@ open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProto
// MARK: - Setup
//--------------------------------------------------
public func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setupUpdateView()
setup()
}
}
@ -96,15 +79,17 @@ open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProto
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
fatalError("Implement updateView")
open override func layoutSubviews() {
super.layoutSubviews()
updateView()
}
open func updateView() {}
open func reset() {
backgroundColor = .clear
if let model = model as? Resettable {
model.reset()
}
surface = .light
disabled = false
}
// MARK: - ViewProtocol

View File

@ -8,12 +8,12 @@
import Foundation
import UIKit
public class SelectorGroupHandlerBase<GroupModelType: SelectorGroupModelable, ModelHandlerType: Control<GroupModelType.SelectorModelType>>: Control<GroupModelType> {
public class SelectorGroupHandlerBase<HandlerType: Control>: Control {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var selectorViews: [ModelHandlerType] = []
public var selectorViews: [HandlerType] = []
//--------------------------------------------------
// MARK: - Overrides
@ -34,68 +34,19 @@ public class SelectorGroupHandlerBase<GroupModelType: SelectorGroupModelable, Mo
}
}
public func findSelectorView(id: UUID?) -> ModelHandlerType? {
return selectorViews.first(where: { existingSelectorView in
return existingSelectorView.model.id == id
})
}
public func getCachedSelector(viewModel: ModelHandlerType.ModelType) -> ModelHandlerType.ModelType? {
if let index = model.selectors.firstIndex(where: { element in
return element.id == viewModel.id
}) {
return model.selectors[index]
} else {
return nil
}
}
public func replace(viewModel: ModelHandlerType.ModelType){
if let index = model.selectors.firstIndex(where: { element in
return element.id == viewModel.id
}) {
model.selectors[index] = viewModel
}
}
public func createModelHandler(selector: ModelHandlerType.ModelType) -> ModelHandlerType {
//create view
let newSelectorView = ModelHandlerType(with: selector)
//add model update to the subscribers
newSelectorView
.modelPublisher
.sink { [weak self] model in
self?.replace(viewModel: model)
}
.store(in: &subscribers)
//add the selectedPublisher for the change
newSelectorView
.publisher(for: .touchUpInside)
.sink { [weak self] control in
self?.didSelect(control)
}
.store(in: &subscribers)
return newSelectorView
}
open func didSelect(_ selectedControl: ModelHandlerType) {
open func didSelect(_ selectedControl: HandlerType) {
fatalError("Must override didSelect")
}
public func valueChanged() {
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.ModelStateDebounce) { [weak self] in
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.StateDebounce) { [weak self] in
self?.sendActions(for: .valueChanged)
}
}
}
public class SelectorGroupSelectedHandlerBase<GroupModelType: SelectorGroupSelectedModelable, ModelHandlerType: Control<GroupModelType.SelectorModelType>>: SelectorGroupHandlerBase<GroupModelType, ModelHandlerType> where GroupModelType.SelectorModelType == ModelHandlerType.ModelType {
public var selectedModel: ModelHandlerType.ModelType? {
return model.selectedModel
public class SelectorGroupSelectedHandlerBase<HandlerType: Control>: SelectorGroupHandlerBase<HandlerType>{
public var selectedHandler: HandlerType? {
return selectorViews.filter { $0.isSelected == true }.first
}
}

View File

@ -10,39 +10,32 @@ import UIKit
import Combine
open class View<ModelType: Modelable>: UIView, ModelHandlerable, ViewProtocol, Resettable {
@objc(VDSView)
open class View: UIView, Handlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
private var initialSetupPerformed = false
@Proxy(\.model.surface)
open var surface: Surface
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var surface: Surface = .light { didSet { didChange() }}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
didChange()
}
}
@ -53,12 +46,6 @@ open class View<ModelType: Modelable>: UIView, ModelHandlerable, ViewProtocol, R
super.init(frame: .zero)
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
@ -74,26 +61,27 @@ open class View<ModelType: Modelable>: UIView, ModelHandlerable, ViewProtocol, R
// MARK: - Setup
//--------------------------------------------------
public func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setupUpdateView()
setup()
}
}
open override func layoutSubviews() {
super.layoutSubviews()
updateView()
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
fatalError("Implement updateView")
}
open func updateView() {}
open func reset() {
backgroundColor = .clear
if let model = model as? Resettable {
model.reset()
}
surface = .light
disabled = false
}
// MARK: - ViewProtocol

View File

@ -11,44 +11,37 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class Badge: BadgeBase<DefaultBadgeModel>{}
public enum BadgeFillColor: String, Codable, CaseIterable {
case red, yellow, green, orange, blue, black, white
}
open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
@objc(VDSBadge)
public class Badge: View, Accessable {
private var label = Label()
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@Proxy(\.model.fillColor)
open var fillColor: BadgeFillColor
open var fillColor: BadgeFillColor = .red { didSet { didChange() }}
@Proxy(\.model.text)
open var text: String
open var text: String = "" { didSet { didChange() }}
@Proxy(\.model.maxWidth)
open var maxWidth: CGFloat?
open var maxWidth: CGFloat? { didSet { didChange() }}
@Proxy(\.model.numberOfLines)
open var numberOfLines: Int
open var numberOfLines: Int = 1 { didSet { didChange() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { didChange() }}
//--------------------------------------------------
// MARK: - Constraints
@ -90,9 +83,9 @@ open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
//--------------------------------------------------
// MARK: - Configuration
//--------------------------------------------------
public func backgroundColor(for fillColor: BadgeFillColor) -> UIColor {
public func backgroundColor() -> UIColor {
var config: SurfaceColorConfiguration
switch model.fillColor {
switch fillColor {
case .red:
config = SurfaceColorConfiguration().with {
$0.lightColor = VDSColor.backgroundBrandhighlight
@ -130,10 +123,10 @@ open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
}
}
return config.getColor(model)
return config.getColor(self)
}
public func textColorConfiguration(for fillColor: BadgeFillColor) -> AnyColorable {
public func textColorConfiguration() -> AnyColorable {
switch fillColor {
@ -165,19 +158,23 @@ open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
backgroundColor = backgroundColor(for: viewModel.fillColor)
open override func updateView() {
backgroundColor = backgroundColor()
label.textColorConfiguration = textColorConfiguration()
label.numberOfLines = numberOfLines
label.textPosition = .left
label.typograpicalStyle = .BoldBodySmall
label.text = text
label.surface = surface
label.disabled = disabled
label.textColorConfiguration = textColorConfiguration(for: viewModel.fillColor)
label.numberOfLines = viewModel.numberOfLines
if let maxWidth = viewModel.maxWidth, let minWidth = minWidthConstraint?.constant, maxWidth > minWidth {
if let maxWidth = maxWidth, let minWidth = minWidthConstraint?.constant, maxWidth > minWidth {
maxWidthConstraint?.constant = maxWidth
maxWidthConstraint?.isActive = true
} else {
maxWidthConstraint?.isActive = false
}
label.set(with: viewModel.label)
setAccessibilityLabel()
}

View File

@ -1,53 +0,0 @@
//
// BadgeModel.swift
// VDS
//
// Created by Matt Bruce on 9/22/22.
//
import Foundation
import UIKit
import VDSColorTokens
public enum BadgeFillColor: String, Codable, CaseIterable {
case red, yellow, green, orange, blue, black, white
}
public protocol BadgeModel: Modelable, Accessable {
var fillColor: BadgeFillColor { get set }
var text: String { get set }
var maxWidth: CGFloat? { get set }
var numberOfLines: Int { get set }
}
extension BadgeModel {
public var label: DefaultLabelModel {
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BoldBodySmall
model.text = text
model.surface = surface
model.disabled = disabled
return model
}
}
public struct DefaultBadgeModel: BadgeModel {
public var id = UUID()
public var fillColor: BadgeFillColor = .red
public var text: String = ""
public var maxWidth: CGFloat?
public var numberOfLines: Int = 1
public var surface: Surface = .light
public var disabled: Bool = false
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public init() { }
}

View File

@ -11,15 +11,18 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class Button:ButtonBase<DefaultButtonModel>{}
public enum ButtonSize: String, Codable, CaseIterable {
case large
case small
}
open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewProtocol, Resettable {
@objc(VDSButton)
open class Button: UIButton, Handlerable, ViewProtocol, Resettable, Useable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
@ -32,39 +35,29 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@Proxy(\.model.surface)
open var surface: Surface
open var text: String? { didSet { didChange() } }
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
isEnabled = !disabled
}
}
open var use: Use = .primary { didSet { didChange() }}
open var size: ButtonSize = .large { didSet { didChange() }}
@Proxy(\.model.text)
open var text: String?
open var width: CGFloat? { didSet { didChange() }}
@Proxy(\.model.use)
open var use: Use
@Proxy(\.model.size)
open var size: ButtonSize
@Proxy(\.model.width)
open var width: CGFloat?
open var surface: Surface = .light { didSet { didChange() }}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open override var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
didChange()
}
}
//--------------------------------------------------
// MARK: - Configuration Properties
@ -113,16 +106,9 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public required init?(coder: NSCoder) {
@ -138,7 +124,6 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
accessibilityTraits = .staticText
setupUpdateView()
setup()
}
@ -149,39 +134,44 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
//only 1 of the 2 widths can be on at the same time
widthConstraint = widthAnchor.constraint(equalToConstant: 0)
minWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: model.size.minimumWidth)
minWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: size.minimumWidth)
//height
heightConstraint = heightAnchor.constraint(equalToConstant: model.size.height)
heightConstraint = heightAnchor.constraint(equalToConstant: size.height)
heightConstraint?.isActive = true
}
open func reset() {
model = ModelType()
surface = .light
disabled = false
use = .primary
width = nil
size = .large
accessibilityCustomActions = []
accessibilityTraits = .staticText
}
open override func layoutSubviews() {
super.layoutSubviews()
updateView()
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
open func updateView() {
let bgColor = buttonBackgroundColorConfiguration.getColor(viewModel)
let borderColor = buttonBorderColorConfiguration.getColor(viewModel)
let titleColor = buttonTitleColorConfiguration.getColor(viewModel)
let borderWidth = viewModel.use == .secondary ? 1.0 : 0.0
let buttonHeight = viewModel.size.height
let bgColor = buttonBackgroundColorConfiguration.getColor(self)
let borderColor = buttonBorderColorConfiguration.getColor(self)
let titleColor = buttonTitleColorConfiguration.getColor(self)
let borderWidth = use == .secondary ? 1.0 : 0.0
let buttonHeight = size.height
let cornerRadius = buttonHeight / 2
let minWidth = viewModel.size.minimumWidth
let font = viewModel.size == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font
let edgeInsets = viewModel.size.edgeInsets
let minWidth = size.minimumWidth
let font = size == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font
let edgeInsets = size.edgeInsets
if let text = viewModel.text {
setTitle(text, for: .normal)
} else {
setTitle("No ViewModel Text", for: .normal)
}
setTitle(text ?? "No Text", for: .normal)
titleLabel?.font = font
backgroundColor = bgColor
setTitleColor(titleColor, for: .normal)
@ -193,7 +183,7 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
minWidthConstraint?.constant = minWidth
heightConstraint?.constant = buttonHeight
if let width = viewModel.width, width > minWidth {
if let width, width > minWidth {
widthConstraint?.constant = width
widthConstraint?.isActive = true
minWidthConstraint?.isActive = false
@ -202,20 +192,20 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
minWidthConstraint?.isActive = true
}
}
//--------------------------------------------------
// MARK: - PRIVATE
//--------------------------------------------------
private class UseableColorConfiguration: ObjectColorable {
typealias ModelType = Disabling & Surfaceable & Useable
typealias ObjectType = Disabling & Surfaceable & Useable
public var primary = DisabledSurfaceColorConfiguration()
public var secondary = DisabledSurfaceColorConfiguration()
required public init(){}
public func getColor(_ viewModel: ModelType) -> UIColor {
return viewModel.use == .primary ? primary.getColor(viewModel) : secondary.getColor(viewModel)
public func getColor(_ object: ObjectType) -> UIColor {
return object.use == .primary ? primary.getColor(object) : secondary.getColor(object)
}
}

View File

@ -1,34 +0,0 @@
//
// ButtonModel.swift
// VDS
//
// Created by Jarrod Courtney on 9/16/22.
//
import Foundation
import UIKit
public enum ButtonSize: String, Codable, CaseIterable {
case large
case small
}
public protocol ButtonModel: Modelable, Useable {
var text: String? { get set }
var width: CGFloat? { get set }
var size: ButtonSize { get set }
var use: Use { get set }
}
public struct DefaultButtonModel: ButtonModel {
public var id = UUID()
public var text: String?
public var typograpicalStyle: TypographicalStyle = .BoldBodyLarge
public var surface: Surface = .light
public var use: Use = .primary
public var disabled: Bool = false
public var width: CGFloat?
public var size: ButtonSize = .large
public init(){}
}

View File

@ -11,9 +11,11 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class Checkbox: CheckboxBase<DefaultCheckboxModel>{}
@objc(VDSCheckbox)
public class Checkbox: CheckboxBase{}
public class SoloCheckbox: CheckboxBase<DefaultCheckboxModel>{
@objc(VDSSoloCheckbox)
public class SoloCheckbox: CheckboxBase{
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -23,11 +25,36 @@ public class SoloCheckbox: CheckboxBase<DefaultCheckboxModel>{
}
}
open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
@objc(VDSCheckboxBase)
open class CheckboxBase: Control, Accessable, DataTrackable, BinaryColorable, Errorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var shouldShowError: Bool {
guard showError && !disabled && errorText?.isEmpty == false else { return false }
return true
}
private var shouldShowLabels: Bool {
guard labelText?.isEmpty == false || childText?.isEmpty == false else { return false }
return true
}
private var mainStackView: UIStackView = {
return UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
@ -67,60 +94,42 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
}()
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
open override var isSelected: Bool { didSet { didChange() }}
@Proxy(\.model.labelText)
open var labelText: String?
open var labelText: String? { didSet { didChange() }}
@Proxy(\.model.childText)
open var childText: String?
open var labelTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
@Proxy(\.model.showError)
open var showError: Bool
@Proxy(\.model.errorText)
open var errorText: String?
open var childText: String? { didSet { didChange() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var childTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
@Proxy(\.model.value)
open var value: AnyHashable?
open var showError: Bool = false { didSet { didChange() }}
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var errorText: String? { didSet { didChange() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var inputId: String? { didSet { didChange() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var value: AnyHashable? { didSet { didChange() }}
open var dataAnalyticsTrack: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var dataClickStream: String? { didSet { didChange() }}
open var dataTrack: String? { didSet { didChange() }}
open var accessibilityHintEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { didChange() }}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
@ -159,7 +168,7 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width)
selectorWidthConstraint?.isActive = true
updateSelector(model)
updateSelector()
mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -168,22 +177,32 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
}
func updateLabels(_ viewModel: ModelType) {
func updateLabels() {
//deal with labels
if viewModel.shouldShowLabels {
if shouldShowLabels {
//add the stackview to hold the 2 labels
//top label
if let labelModel = viewModel.labelModel {
primaryLabel.set(with: labelModel)
if let labelText {
primaryLabel.textPosition = .left
primaryLabel.typograpicalStyle = .BoldBodyLarge
primaryLabel.text = labelText
primaryLabel.surface = surface
primaryLabel.disabled = disabled
primaryLabel.attributes = labelTextAttributes
primaryLabel.isHidden = false
} else {
primaryLabel.isHidden = true
}
//bottom label
if let childModel = viewModel.childModel {
secondaryLabel.set(with: childModel)
if let childText {
secondaryLabel.textPosition = .left
secondaryLabel.typograpicalStyle = .BodyLarge
secondaryLabel.text = childText
secondaryLabel.surface = surface
secondaryLabel.disabled = disabled
secondaryLabel.attributes = childTextAttributes
secondaryLabel.isHidden = false
} else {
secondaryLabel.isHidden = true
@ -199,20 +218,23 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
}
//either add/remove the error from the main stack
if let errorModel = model.errorModel, model.shouldShowError {
errorLabel.set(with: errorModel)
if let errorText, shouldShowError {
errorLabel.textPosition = .left
errorLabel.typograpicalStyle = .BodyMedium
errorLabel.text = errorText
errorLabel.surface = surface
errorLabel.disabled = disabled
mainStackView.spacing = 8
errorLabel.isHidden = false
} else {
mainStackView.spacing = 0
errorLabel.isHidden = true
}
}
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -229,14 +251,12 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabels(viewModel)
updateSelector(viewModel)
open override func updateView() {
updateLabels()
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setNeedsLayout()
layoutIfNeeded()
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
}
@ -294,11 +314,11 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
return checkboxSize
}
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
//get the colors
let backgroundColor = checkboxBackgroundColorConfiguration.getColor(viewModel)
let borderColor = checkboxBorderColorConfiguration.getColor(viewModel)
let checkColor = checkboxCheckColorConfiguration.getColor(viewModel)
let backgroundColor = checkboxBackgroundColorConfiguration.getColor(self)
let borderColor = checkboxBorderColorConfiguration.getColor(self)
let checkColor = checkboxCheckColorConfiguration.getColor(self)
if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) {
shapeLayer.removeFromSuperlayer()
@ -340,7 +360,7 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
shapeLayer.lineJoin = .miter
shapeLayer.lineWidth = 2
CATransaction.withDisabledAnimations {
shapeLayer.strokeEnd = model.selected ? 1 : 0
shapeLayer.strokeEnd = isSelected ? 1 : 0
}
}
}
@ -350,21 +370,21 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
// MARK: - Color Class Configurations
//--------------------------------------------------
internal class ErrorBinaryDisabledSurfaceColorConfiguration: BinaryDisabledSurfaceColorable {
typealias ModelType = Errorable & Disabling & Surfaceable & BinaryColorable
typealias ObjectType = Errorable & Disabling & Surfaceable & BinaryColorable
var error = BinarySurfaceColorConfiguration()
var forTrue = DisabledSurfaceColorConfiguration()
var forFalse = DisabledSurfaceColorConfiguration()
required init() {}
func getColor(_ viewModel: ModelType) -> UIColor {
func getColor(_ object: ObjectType) -> UIColor {
//only show error is enabled and showError == true
let showErrorColor = !viewModel.disabled && viewModel.showError
let showErrorColor = !object.disabled && object.showError
if showErrorColor {
return error.getColor(viewModel)
return error.getColor(object)
} else {
return getBinaryColor(viewModel)
return getBinaryColor(object)
}
}
}

View File

@ -8,7 +8,8 @@
import Foundation
import UIKit
public class CheckboxGroup: CheckboxGroupBase<DefaultCheckboxGroupModel, Checkbox> {
@objc(VDSCheckboxGroup)
public class CheckboxGroup: CheckboxGroupBase<Checkbox> {
public override func didSelect(_ selectedControl: Checkbox) {
selectedControl.toggle()
if selectedControl.isSelected, showError{
@ -18,13 +19,30 @@ public class CheckboxGroup: CheckboxGroupBase<DefaultCheckboxGroupModel, Checkbo
}
}
public class CheckboxGroupBase<GroupModelType: CheckboxGroupModel, ModelHandlerType: CheckboxBase<GroupModelType.SelectorModelType>>: SelectorGroupHandlerBase<GroupModelType, ModelHandlerType> {
public class CheckboxGroupBase<HandlerType: CheckboxBase>: SelectorGroupHandlerBase<HandlerType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [HandlerType] {
didSet {
for selector in selectorViews {
if !mainStackView.arrangedSubviews.contains(selector) {
selector
.publisher(for: .touchUpInside)
.sink { [weak self] handler in
self?.didSelect(handler)
}.store(in: &subscribers)
mainStackView.addArrangedSubview(selector)
}
}
}
}
private var _showError: Bool = false
public var showError: Bool {
get { model.showError }
get { _showError }
set {
var newShowError = newValue
let anySelected = selectorViews.filter { $0.isSelected == true }.count > 0
@ -34,8 +52,7 @@ public class CheckboxGroupBase<GroupModelType: CheckboxGroupModel, ModelHandlerT
selectorViews.forEach { handler in
handler.showError = newShowError
}
model.showError = newShowError
_showError = newShowError
}
}
@ -67,24 +84,8 @@ public class CheckboxGroupBase<GroupModelType: CheckboxGroupModel, ModelHandlerT
mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
open override func updateView(viewModel: ModelType) {
for selectorModel in viewModel.selectors {
//see if view is there for the model
if let foundSelectorView = findSelectorView(id: selectorModel.id) {
foundSelectorView.set(with: selectorModel)
} else {
//create view
let newSelectorView = createModelHandler(selector: selectorModel)
self.selectorViews.append(newSelectorView)
mainStackView.addArrangedSubview(newSelectorView)
}
}
}
public var selectedModelHandlers: [ModelHandlerType]? {
public var selectedHandlers: [HandlerType]? {
let selected = selectorViews.filter{ $0.isSelected == true }
guard selected.count > 0 else { return nil }
return selected

View File

@ -1,29 +0,0 @@
//
// CheckboxGroupModel.swift
// VDS
//
// Created by Matt Bruce on 8/23/22.
//
import Foundation
public protocol CheckboxGroupModel: SelectorGroupModelable, Errorable where SelectorModelType: CheckboxModel {
}
public struct DefaultCheckboxGroupModel: CheckboxGroupModel {
public typealias SelectorModelType = DefaultCheckboxModel
public var id = UUID()
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var selectors: [SelectorModelType]
public var showError: Bool = false
public var errorText: String?
public init() { selectors = [] }
public init(selectors: [SelectorModelType]){
self.selectors = selectors
}
}

View File

@ -1,123 +0,0 @@
//
// ToggleModel.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
//
import Foundation
import UIKit
public protocol CheckboxModel: Modelable, FormFieldable, Errorable, DataTrackable, Accessable, Selectable, BinaryColorable {
var labelText: String? { get set }
var labelTextAttributes: [any LabelAttributeModel]? { get set }
var childText: String? { get set }
var childTextAttributes: [any LabelAttributeModel]? { get set }
}
extension CheckboxModel {
public var shouldShowError: Bool {
guard showError && !disabled && errorText?.isEmpty == false else { return false }
return true
}
public var shouldShowLabels: Bool {
guard labelText?.isEmpty == false || childText?.isEmpty == false else { return false }
return true
}
public var labelModel: DefaultLabelModel? {
guard let labelText = labelText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BoldBodyLarge
model.text = labelText
model.surface = surface
model.disabled = disabled
model.attributes = labelTextAttributes
return model
}
public var childModel: DefaultLabelModel? {
guard let childText = childText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodyLarge
model.text = childText
model.surface = surface
model.disabled = disabled
model.attributes = childTextAttributes
return model
}
public var errorModel: DefaultLabelModel? {
guard let errorText = errorText, showError else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodyMedium
model.text = errorText
model.surface = surface
model.disabled = disabled
return model
}
}
public struct DefaultCheckboxModel: CheckboxModel {
public var id = UUID()
public var selected: Bool = false
public var labelText: String?
public var labelTextAttributes: [any LabelAttributeModel]?
public var childText: String?
public var childTextAttributes: [any LabelAttributeModel]?
public var showError: Bool = false
public var errorText: String?
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var dataAnalyticsTrack: String?
public var dataClickStream: String?
public var dataTrack: String?
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public init() {}
public static func == (lhs: DefaultCheckboxModel, rhs: DefaultCheckboxModel) -> Bool {
lhs.isEqual(rhs)
}
public func isEqual(_ equatable: DefaultCheckboxModel) -> Bool {
return id == equatable.id
&& selected == equatable.selected
&& labelText == equatable.labelText
&& labelTextAttributes == equatable.labelTextAttributes
&& childText == equatable.childText
&& childTextAttributes == equatable.childTextAttributes
&& showError == equatable.showError
&& errorText == equatable.errorText
&& inputId == equatable.inputId
&& value == equatable.value
&& surface == equatable.surface
&& disabled == equatable.disabled
&& dataAnalyticsTrack == equatable.dataAnalyticsTrack
&& dataClickStream == equatable.dataClickStream
&& dataTrack == equatable.dataTrack
&& accessibilityHintEnabled == equatable.accessibilityHintEnabled
&& accessibilityHintDisabled == equatable.accessibilityHintDisabled
&& accessibilityValueEnabled == equatable.accessibilityValueEnabled
&& accessibilityValueDisabled == equatable.accessibilityValueDisabled
&& accessibilityLabelEnabled == equatable.accessibilityLabelEnabled
&& accessibilityLabelDisabled == equatable.accessibilityLabelDisabled
}
}

View File

@ -33,7 +33,7 @@ public struct UnderlineLabelAttribute: LabelAttributeModel {
}
}
public init(location: Int, length: Int, style: UnderlineStyle = .single, color: UIColor = .black, pattern: UnderlineStyle.Pattern? = nil) {
public init(location: Int, length: Int, style: UnderlineStyle = .single, color: UIColor? = nil, pattern: UnderlineStyle.Pattern? = nil) {
self.location = location
self.length = length
self.color = color

View File

@ -10,57 +10,46 @@ import UIKit
import VDSColorTokens
import Combine
public class Label:LabelBase<DefaultLabelModel>{}
@objc(VDSLabel)
public class Label: LabelBase {}
open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProtocol, Resettable {
@objc(VDSLabelBase)
open class LabelBase: UILabel, Handlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
public var hasChanged: Bool = false
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@Proxy(\.model.surface)
open var surface: Surface
open var surface: Surface = .light { didSet { didChange() }}
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open var attributes: [any LabelAttributeModel]? { didSet { didChange() }}
open var typograpicalStyle: TypographicalStyle = .defaultStyle { didSet { didChange() }}
open var textPosition: TextPosition = .left { didSet { didChange() }}
open override var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
didChange()
}
}
@Proxy(\.model.attributes)
open var attributes: [any LabelAttributeModel]?
@Proxy(\.model.typograpicalStyle)
open var typograpicalStyle: TypographicalStyle
@Proxy(\.model.textPosition)
open var textPosition: TextPosition
//can't use @Proxy here
override open var text: String? {
didSet {
if model.text != oldValue {
model.text = text
}
didChange()
}
}
@ -81,17 +70,10 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
super.init(frame: .zero)
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public required init?(coder: NSCoder) {
@ -109,37 +91,38 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
accessibilityTraits = .staticText
setupUpdateView()
setup()
}
open func setup() {}
open func reset() {
surface = .light
disabled = false
attributes = nil
typograpicalStyle = .defaultStyle
textPosition = .left
text = nil
attributedText = nil
textColor = .black
font = TypographicalStyle.BodyLarge.font
textAlignment = .left
accessibilityCustomActions = []
accessibilityTraits = .staticText
numberOfLines = 0
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
textAlignment = viewModel.textPosition.textAlignment
textColor = textColorConfiguration.getColor(viewModel)
open override func layoutSubviews() {
super.layoutSubviews()
updateView()
}
open func updateView() {
textAlignment = textPosition.textAlignment
textColor = textColorConfiguration.getColor(self)
font = typograpicalStyle.font
if let vdsFont = viewModel.font {
font = vdsFont
} else {
font = TypographicalStyle.defaultStyle.font
}
if let text = viewModel.text, let font = font, let textColor = textColor {
if let text = text, let font = font, let textColor = textColor {
//clear the arrays holding actions
accessibilityCustomActions = []
actions = []
@ -149,9 +132,9 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
let mutableText = NSMutableAttributedString(string: text, attributes: startingAttributes)
//set the local lineHeight/lineSpacing attributes
setStyleAttributes(viewModel: viewModel, attributedString: mutableText)
setStyleAttributes(attributedString: mutableText)
if let attributes = viewModel.attributes {
if let attributes = attributes {
//loop through the models attributes
for attribute in attributes {
@ -170,42 +153,40 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
}
//only enabled if enabled and has actions
isUserInteractionEnabled = !viewModel.disabled && !actions.isEmpty
isUserInteractionEnabled = !disabled && !actions.isEmpty
//set the attributed text
attributedText = mutableText
} else {
text = viewModel.text
}
}
// MARK: - Private Attributes
private func setStyleAttributes(viewModel: ModelType, attributedString: NSMutableAttributedString) {
private func setStyleAttributes(attributedString: NSMutableAttributedString) {
//get the range
let entireRange = NSRange(location: 0, length: attributedString.length)
//set letterSpacing
if viewModel.typograpicalStyle.letterSpacing > 0.0 {
attributedString.addAttribute(.kern, value: viewModel.typograpicalStyle.letterSpacing, range: entireRange)
if typograpicalStyle.letterSpacing > 0.0 {
attributedString.addAttribute(.kern, value: typograpicalStyle.letterSpacing, range: entireRange)
}
//set lineHeight
if viewModel.typograpicalStyle.lineHeight > 0.0 {
let lineHeight = viewModel.typograpicalStyle.lineHeight
if typograpicalStyle.lineHeight > 0.0 {
let lineHeight = typograpicalStyle.lineHeight
let adjustment = lineHeight > font.lineHeight ? 2.0 : 1.0
let baselineOffset = (lineHeight - font.lineHeight) / 2.0 / adjustment
let paragraph = NSMutableParagraphStyle().with {
$0.maximumLineHeight = lineHeight
$0.minimumLineHeight = lineHeight
$0.alignment = viewModel.textPosition.textAlignment
$0.alignment = textPosition.textAlignment
$0.lineBreakMode = lineBreakMode
}
attributedString.addAttribute(.baselineOffset, value: baselineOffset, range: entireRange)
attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange)
} else if viewModel.textPosition != .left {
} else if textPosition != .left {
let paragraph = NSMutableParagraphStyle().with {
$0.alignment = viewModel.textPosition.textAlignment
$0.alignment = textPosition.textAlignment
$0.lineBreakMode = lineBreakMode
}
attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange)

View File

@ -1,40 +0,0 @@
//
// VDSLabelModel.swift
// VDS
//
// Created by Matt Bruce on 7/28/22.
//
import Foundation
import UIKit
public protocol LabelModel: Modelable, Labelable, Equatable, AnyEquatable {
var text: String? { get set }
var attributes: [any LabelAttributeModel]? { get set }
}
public struct DefaultLabelModel: LabelModel {
public static func == (lhs: DefaultLabelModel, rhs: DefaultLabelModel) -> Bool {
lhs.isEqual(rhs)
}
public func isEqual(_ equatable: DefaultLabelModel) -> Bool {
return id == equatable.id
&& attributes == equatable.attributes
&& text == equatable.text
&& surface == equatable.surface
&& typograpicalStyle == equatable.typograpicalStyle
&& textPosition == equatable.textPosition
&& surface == equatable.surface
&& disabled == equatable.disabled
}
public var id = UUID()
public var text: String?
public var attributes: [any LabelAttributeModel]?
public var typograpicalStyle: TypographicalStyle = .BodySmall
public var textPosition: TextPosition = .left
public var surface: Surface = .light
public var disabled: Bool = false
public init(){}
}

View File

@ -11,9 +11,11 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class RadioBox: RadioBoxBase<DefaultRadioBoxModel>{}
@objc(VDSRadioBox)
public class RadioBox: RadioBoxBase{}
public class SolorRadioBox: RadioBoxBase<DefaultRadioBoxModel>{
@objc(VDSSoloRadioBox)
public class SoloRadioBox: RadioBoxBase{
public override func initialSetup() {
super.initialSetup()
@ -24,8 +26,23 @@ public class SolorRadioBox: RadioBoxBase<DefaultRadioBoxModel>{
}
}
open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
@objc(VDSRadioBoxBase)
open class RadioBoxBase: Control, BinaryColorable, Accessable, DataTrackable{
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -67,61 +84,42 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}()
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
@Proxy(\.model.text)
open var text: String
open var text: String = "Default Text" { didSet { didChange() }}
@Proxy(\.model.subText)
open var subText: String?
open var textAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
open var subText: String? { didSet { didChange() }}
@Proxy(\.model.subTextRight)
open var subTextRight: String?
open var subTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
@Proxy(\.model.strikethrough)
open var strikethrough: Bool
open var subTextRight: String? { didSet { didChange() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var subTextRightAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
@Proxy(\.model.value)
open var value: AnyHashable?
open var strikethrough: Bool = false { didSet { didChange() }}
open var inputId: String? { didSet { didChange() }}
open var value: AnyHashable? { didSet { didChange() }}
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var dataAnalyticsTrack: String? { didSet { didChange() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var dataClickStream: String? { didSet { didChange() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var dataTrack: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { didChange() }}
//functions
//--------------------------------------------------
@ -153,7 +151,7 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
selectorLeftLabelStackView.spacing = 4
selectorLeftLabelStackView.isHidden = false
updateSelector(model)
updateSelector()
selectorView.topAnchor.constraint(equalTo: topAnchor).isActive = true
selectorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -167,23 +165,38 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
}
func updateLabels(_ viewModel: ModelType) {
func updateLabels() {
//add the stackview to hold the 2 labels
//text label
textLabel.set(with: viewModel.textModel)
textLabel.textPosition = .left
textLabel.typograpicalStyle = .BoldBodyLarge
textLabel.text = text
textLabel.surface = surface
textLabel.disabled = disabled
textLabel.attributes = textAttributes
//subText label
if let subTextModel = viewModel.subTextModel {
subTextLabel.set(with: subTextModel)
if let subText {
subTextLabel.textPosition = .left
subTextLabel.typograpicalStyle = .BodyLarge
subTextLabel.text = subText
subTextLabel.surface = surface
subTextLabel.disabled = disabled
subTextLabel.attributes = subTextAttributes
subTextLabel.isHidden = false
} else {
subTextLabel.isHidden = true
}
//subTextRight label
if let subTextRightModel = viewModel.subTextRightModel {
subTextRightLabel.set(with: subTextRightModel)
if let subTextRight {
subTextRightLabel.textPosition = .right
subTextRightLabel.typograpicalStyle = .BodyLarge
subTextRightLabel.text = subTextRight
subTextRightLabel.surface = surface
subTextRightLabel.disabled = disabled
subTextRightLabel.attributes = subTextRightAttributes
subTextRightLabel.isHidden = false
} else {
subTextRightLabel.isHidden = true
@ -192,7 +205,7 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -206,14 +219,12 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabels(viewModel)
updateSelector(viewModel)
open override func updateView() {
updateLabels()
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setNeedsLayout()
layoutIfNeeded()
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
}
@ -256,11 +267,11 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
private var shapeLayer: CAShapeLayer?
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
//get the colors
let backgroundColor = radioBoxBackgroundColorConfiguration.getColor(viewModel)
let borderColor = radioBoxBorderColorConfiguration.getColor(viewModel)
let borderWidth = viewModel.selected ? selectorBorderWidthSelected : selectorBorderWidth
let backgroundColor = radioBoxBackgroundColorConfiguration.getColor(self)
let borderColor = radioBoxBorderColorConfiguration.getColor(self)
let borderWidth = isSelected ? selectorBorderWidthSelected : selectorBorderWidth
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor
@ -278,12 +289,12 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
open override func draw(_ layer: CALayer, in ctx: CGContext) {
let borderColor = radioBoxBorderColorConfiguration.getColor(model)
let borderColor = radioBoxBorderColorConfiguration.getColor(self)
shapeLayer?.removeFromSuperlayer()
shapeLayer = nil
if model.strikethrough {
if strikethrough {
let bounds = selectorView.bounds
let length = max(bounds.size.height, bounds.size.width)
guard length > 0.0, shapeLayer == nil else { return }

View File

@ -8,7 +8,8 @@
import Foundation
import UIKit
public class RadioBoxGroup: RadioBoxGroupBase<DefaultRadioBoxGroupModel, RadioBox> {
@objc(VDSRadioBoxGroup)
public class RadioBoxGroup: RadioBoxGroupBase<RadioBox> {
public override func didSelect(_ selectedControl: RadioBox) {
let oldSelectedControl = selectorViews.filter { $0.isSelected == true }.first
@ -18,8 +19,26 @@ public class RadioBoxGroup: RadioBoxGroupBase<DefaultRadioBoxGroupModel, RadioBo
}
}
public class RadioBoxGroupBase<GroupModelType: RadioBoxGroupModel, ModelHandlerType: RadioBoxBase<GroupModelType.SelectorModelType>>: SelectorGroupSelectedHandlerBase<GroupModelType, ModelHandlerType> {
public class RadioBoxGroupBase<HandlerType: RadioBoxBase>: SelectorGroupSelectedHandlerBase<HandlerType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [HandlerType] {
didSet {
for selector in selectorViews {
if !mainStackView.arrangedSubviews.contains(selector) {
selector
.publisher(for: .touchUpInside)
.sink { [weak self] handler in
self?.didSelect(handler)
}.store(in: &subscribers)
mainStackView.addArrangedSubview(selector)
}
}
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -68,31 +87,4 @@ public class RadioBoxGroupBase<GroupModelType: RadioBoxGroupModel, ModelHandlerT
}
}.store(in: &subscribers)
}
open override func updateView(viewModel: ModelType) {
for selectorModel in viewModel.selectors {
//see if view is there for the model
if let foundSelectorView = findSelectorView(id: selectorModel.id) {
foundSelectorView.set(with: selectorModel)
} else {
//create view
let newSelectorView = createModelHandler(selector: selectorModel)
self.selectorViews.append(newSelectorView)
mainStackView.addArrangedSubview(newSelectorView)
}
}
}
public var selectedModelHandler: ModelHandlerType? {
if let index = selectorViews.firstIndex(where: { element in
return element.isSelected == true
}) {
return selectorViews[index]
} else {
return nil
}
}
}

View File

@ -1,26 +0,0 @@
//
// RadioBoxGroupModel.swift
// VDS
//
// Created by Matt Bruce on 8/23/22.
//
import Foundation
public protocol RadioBoxGroupModel: SelectorGroupSelectedModelable where SelectorModelType: RadioBoxModel { }
public struct DefaultRadioBoxGroupModel: RadioBoxGroupModel {
public typealias SelectorModelType = DefaultRadioBoxModel
public var id = UUID()
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var selectors: [SelectorModelType]
public var showError: Bool = false
public var errorText: String?
public init() { selectors = [] }
public init(selectors: [SelectorModelType]){
self.selectors = selectors
}
}

View File

@ -1,122 +0,0 @@
//
// RadioBoxModel.swift
// VDS
//
// Created by Matt Bruce on 8/23/22.
//
import Foundation
import UIKit
public protocol RadioBoxModel: Modelable, FormFieldable, DataTrackable, Accessable, Selectable, BinaryColorable {
var text: String { get set }
var textAttributes: [any LabelAttributeModel]? { get set }
var subText: String? { get set }
var subTextAttributes: [any LabelAttributeModel]? { get set }
var subTextRight: String? { get set }
var subTextRightAttributes: [any LabelAttributeModel]? { get set }
var strikethrough: Bool { get set }
}
extension RadioBoxModel {
public var textModel: DefaultLabelModel {
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BoldBodyLarge
model.text = text
model.surface = surface
model.disabled = disabled
model.attributes = textAttributes
return model
}
public var subTextModel: DefaultLabelModel? {
guard let subText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodyLarge
model.text = subText
model.surface = surface
model.disabled = disabled
model.attributes = subTextAttributes
return model
}
public var subTextRightModel: DefaultLabelModel? {
guard let subTextRight else { return nil }
var model = DefaultLabelModel()
model.textPosition = .right
model.typograpicalStyle = .BodyLarge
model.text = subTextRight
model.surface = surface
model.disabled = disabled
model.attributes = subTextRightAttributes
return model
}
}
public struct DefaultRadioBoxModel: RadioBoxModel {
public var id = UUID()
public var selected: Bool = false
public var text: String = "Default Text"
public var textAttributes: [any LabelAttributeModel]?
public var subText: String?
public var subTextAttributes: [any LabelAttributeModel]?
public var subTextRight: String?
public var subTextRightAttributes: [any LabelAttributeModel]?
public var selectedAccentColor: UIColor?
public var strikethrough: Bool = false
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var dataAnalyticsTrack: String?
public var dataClickStream: String?
public var dataTrack: String?
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public init() {}
public static func == (lhs: DefaultRadioBoxModel, rhs: DefaultRadioBoxModel) -> Bool {
lhs.isEqual(rhs)
}
public func isEqual(_ equatable: DefaultRadioBoxModel) -> Bool {
return id == equatable.id
&& selected == equatable.selected
&& text == equatable.text
&& textAttributes == equatable.textAttributes
&& subText == equatable.subText
&& subTextAttributes == equatable.subTextAttributes
&& subTextRight == equatable.subTextRight
&& subTextRightAttributes == equatable.subTextRightAttributes
&& selectedAccentColor == equatable.selectedAccentColor
&& strikethrough == equatable.strikethrough
&& inputId == equatable.inputId
&& value == equatable.value
&& surface == equatable.surface
&& disabled == equatable.disabled
&& dataAnalyticsTrack == equatable.dataAnalyticsTrack
&& dataClickStream == equatable.dataClickStream
&& dataTrack == equatable.dataTrack
&& accessibilityHintEnabled == equatable.accessibilityHintEnabled
&& accessibilityHintDisabled == equatable.accessibilityHintDisabled
&& accessibilityValueEnabled == equatable.accessibilityValueEnabled
&& accessibilityValueDisabled == equatable.accessibilityValueDisabled
&& accessibilityLabelEnabled == equatable.accessibilityLabelEnabled
&& accessibilityLabelDisabled == equatable.accessibilityLabelDisabled
}
}

View File

@ -10,7 +10,8 @@ import UIKit
import VDSColorTokens
import VDSFormControlsTokens
public class RadioButton: RadioButtonBase<DefaultRadioButtonModel>{
@objc(VDSRadioButton)
public class RadioButton: RadioButtonBase {
//for groups allows "toggle"
open override func toggle() {
//removed error
@ -21,7 +22,8 @@ public class RadioButton: RadioButtonBase<DefaultRadioButtonModel>{
}
}
public class SoloRadioButton: RadioButtonBase<DefaultRadioButtonModel>{
@objc(VDSSoloRadioButton)
public class SoloRadioButton: RadioButtonBase {
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -31,11 +33,36 @@ public class SoloRadioButton: RadioButtonBase<DefaultRadioButtonModel>{
}
}
open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
@objc(VDSRadioButtonBase)
open class RadioButtonBase: Control, Accessable, DataTrackable, BinaryColorable, Errorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var shouldShowError: Bool {
guard showError && !disabled && errorText?.isEmpty == false else { return false }
return true
}
private var shouldShowLabels: Bool {
guard labelText?.isEmpty == false || childText?.isEmpty == false else { return false }
return true
}
private var mainStackView: UIStackView = {
return UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
@ -73,61 +100,40 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}()
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
@Proxy(\.model.labelText)
open var labelText: String?
open var labelText: String? { didSet { didChange() }}
@Proxy(\.model.childText)
open var childText: String?
open var labelTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
open var childText: String? { didSet { didChange() }}
@Proxy(\.model.showError)
open var showError: Bool
@Proxy(\.model.errorText)
open var errorText: String?
open var childTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var showError: Bool = false { didSet { didChange() }}
open var errorText: String? { didSet { didChange() }}
@Proxy(\.model.value)
open var value: AnyHashable?
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var inputId: String? { didSet { didChange() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var value: AnyHashable? { didSet { didChange() }}
open var dataAnalyticsTrack: String? { didSet { didChange() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var dataClickStream: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var dataTrack: String? { didSet { didChange() }}
open var accessibilityHintEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { didChange() }}
//--------------------------------------------------
// MARK: - Constraints
@ -167,7 +173,7 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width)
selectorWidthConstraint?.isActive = true
updateSelector(model)
updateSelector()
mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -176,22 +182,32 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
}
func updateLabels(_ viewModel: ModelType) {
func updateLabels() {
//deal with labels
if viewModel.shouldShowLabels {
if shouldShowLabels {
//add the stackview to hold the 2 labels
//top label
if let labelModel = viewModel.labelModel {
primaryLabel.set(with: labelModel)
if let labelText {
primaryLabel.textPosition = .left
primaryLabel.typograpicalStyle = .BoldBodyLarge
primaryLabel.text = labelText
primaryLabel.surface = surface
primaryLabel.disabled = disabled
primaryLabel.attributes = labelTextAttributes
primaryLabel.isHidden = false
} else {
primaryLabel.isHidden = true
}
//bottom label
if let childModel = viewModel.childModel {
secondaryLabel.set(with: childModel)
if let childText {
secondaryLabel.textPosition = .left
secondaryLabel.typograpicalStyle = .BodyLarge
secondaryLabel.text = childText
secondaryLabel.surface = surface
secondaryLabel.disabled = disabled
secondaryLabel.attributes = childTextAttributes
secondaryLabel.isHidden = false
} else {
secondaryLabel.isHidden = true
@ -207,8 +223,12 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
}
//either add/remove the error from the main stack
if let errorModel = model.errorModel, model.shouldShowError {
errorLabel.set(with: errorModel)
if let errorText, shouldShowError {
errorLabel.textPosition = .left
errorLabel.typograpicalStyle = .BodyMedium
errorLabel.text = errorText
errorLabel.surface = surface
errorLabel.disabled = disabled
mainStackView.spacing = 8
errorLabel.isHidden = false
} else {
@ -220,7 +240,7 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -239,14 +259,12 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabels(viewModel)
updateSelector(viewModel)
open override func updateView() {
updateLabels()
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setNeedsLayout()
layoutIfNeeded()
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
}
//--------------------------------------------------
@ -295,7 +313,7 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
radioButtonSize
}
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) {
shapeLayer.removeFromSuperlayer()
@ -307,9 +325,9 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
guard length > 0.0, shapeLayer == nil else { return }
//get the colors
let backgroundColor = radioButtonBackgroundColorConfiguration.getColor(viewModel)
let borderColor = radioButtonBorderColorConfiguration.getColor(viewModel)
let radioSelectedColor = radioButtonCheckColorConfiguration.getColor(viewModel)
let backgroundColor = radioButtonBackgroundColorConfiguration.getColor(self)
let borderColor = radioButtonBorderColorConfiguration.getColor(self)
let radioSelectedColor = radioButtonCheckColorConfiguration.getColor(self)
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor

View File

@ -8,11 +8,11 @@
import Foundation
import UIKit
public class RadioButtonGroup: RadioButtonGroupBase<DefaultRadioButtonGroupModel, RadioButton> {
@objc(VDSRadioButtonGroup)
public class RadioButtonGroup: RadioButtonGroupBase<RadioButton> {
public override func didSelect(_ selectedControl: RadioButton) {
let oldSelectedControl = selectorViews.filter { $0.isSelected == true }.first
oldSelectedControl?.toggle()
selectedHandler?.toggle()
selectedControl.toggle()
if showError {
showError = false
@ -21,22 +21,38 @@ public class RadioButtonGroup: RadioButtonGroupBase<DefaultRadioButtonGroupModel
}
}
public class RadioButtonGroupBase<GroupModelType: RadioButtonGroupModel, ModelHandlerType: RadioButtonBase<GroupModelType.SelectorModelType>>: SelectorGroupSelectedHandlerBase<GroupModelType, ModelHandlerType> {
public class RadioButtonGroupBase<HandlerType: RadioButtonBase>: SelectorGroupSelectedHandlerBase<HandlerType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [HandlerType] {
didSet {
for selector in selectorViews {
if !mainStackView.arrangedSubviews.contains(selector) {
selector
.publisher(for: .touchUpInside)
.sink { [weak self] handler in
self?.didSelect(handler)
}.store(in: &subscribers)
mainStackView.addArrangedSubview(selector)
}
}
}
}
private var _showError: Bool = false
public var showError: Bool {
get { model.showError }
get { _showError }
set {
var newShowError = newValue
if selectedModel != nil, newShowError {
if selectedHandler != nil, newShowError {
newShowError = false
}
selectorViews.forEach { handler in
handler.showError = newShowError
}
model.showError = newShowError
_showError = newShowError
}
}
@ -68,30 +84,4 @@ public class RadioButtonGroupBase<GroupModelType: RadioButtonGroupModel, ModelHa
mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
open override func updateView(viewModel: ModelType) {
for selectorModel in viewModel.selectors {
//see if view is there for the model
if let foundSelectorView = findSelectorView(id: selectorModel.id) {
foundSelectorView.set(with: selectorModel)
} else {
//create view
let newSelectorView = createModelHandler(selector: selectorModel)
self.selectorViews.append(newSelectorView)
mainStackView.addArrangedSubview(newSelectorView)
}
}
}
public var selectedModelHandler: ModelHandlerType? {
if let index = selectorViews.firstIndex(where: { element in
return element.isSelected == true
}) {
return selectorViews[index]
} else {
return nil
}
}
}

View File

@ -1,30 +0,0 @@
//
// RadioButtonGroupModel.swift
// VDS
//
// Created by Matt Bruce on 8/11/22.
//
import Foundation
public protocol RadioButtonGroupModel: SelectorGroupSelectedModelable, Errorable where SelectorModelType: RadioButtonModel { }
extension RadioButtonGroupModel {
public var errorText: String? { return nil }
}
public struct DefaultRadioButtonGroupModel: RadioButtonGroupModel {
public typealias SelectorModelType = DefaultRadioButtonModel
public var id = UUID()
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var selectors: [SelectorModelType]
public var showError: Bool = false
public var errorText: String?
public init() { selectors = [] }
public init(selectors: [SelectorModelType]){
self.selectors = selectors
}
}

View File

@ -1,128 +0,0 @@
//
// ToggleModel.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
//
import Foundation
import UIKit
public protocol RadioButtonModel: Modelable, FormFieldable, Errorable, DataTrackable, Accessable, Selectable, BinaryColorable {
var labelText: String? { get set }
var labelTextAttributes: [any LabelAttributeModel]? { get set }
var childText: String? { get set }
var childTextAttributes: [any LabelAttributeModel]? { get set }
}
extension RadioButtonModel {
public var shouldShowError: Bool {
guard showError && !disabled && errorText?.isEmpty == false else { return false }
return true
}
public var shouldShowLabels: Bool {
guard labelText?.isEmpty == false || childText?.isEmpty == false else { return false }
return true
}
public var labelModel: DefaultLabelModel? {
guard let labelText = labelText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BoldBodyLarge
model.text = labelText
model.surface = surface
model.disabled = disabled
model.attributes = labelTextAttributes
return model
}
public var childModel: DefaultLabelModel? {
guard let childText = childText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodyLarge
model.text = childText
model.surface = surface
model.disabled = disabled
model.attributes = childTextAttributes
return model
}
public var errorModel: DefaultLabelModel? {
guard let errorText = errorText, showError else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodyMedium
model.text = errorText
model.surface = surface
model.disabled = disabled
return model
}
}
public struct DefaultRadioButtonModel: RadioButtonModel {
public var id = UUID()
public var selected: Bool = false
public var labelText: String?
public var labelTextAttributes: [any LabelAttributeModel]?
public var childText: String?
public var childTextAttributes: [any LabelAttributeModel]?
public var showError: Bool = false
public var errorText: String?
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var dataAnalyticsTrack: String?
public var dataClickStream: String?
public var dataTrack: String?
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public init() {}
public static func == (lhs: DefaultRadioButtonModel, rhs: DefaultRadioButtonModel) -> Bool {
lhs.isEqual(rhs)
}
public func isEqual(_ equatable: DefaultRadioButtonModel) -> Bool {
return id == equatable.id
&& selected == equatable.selected
&& labelText == equatable.labelText
&& labelTextAttributes == equatable.labelTextAttributes
&& childText == equatable.childText
&& childTextAttributes == equatable.childTextAttributes
&& showError == equatable.showError
&& errorText == equatable.errorText
&& inputId == equatable.inputId
&& value == equatable.value
&& surface == equatable.surface
&& disabled == equatable.disabled
&& dataAnalyticsTrack == equatable.dataAnalyticsTrack
&& dataClickStream == equatable.dataClickStream
&& dataTrack == equatable.dataTrack
&& accessibilityHintEnabled == equatable.accessibilityHintEnabled
&& accessibilityHintDisabled == equatable.accessibilityHintDisabled
&& accessibilityValueEnabled == equatable.accessibilityValueEnabled
&& accessibilityValueDisabled == equatable.accessibilityValueDisabled
&& accessibilityLabelEnabled == equatable.accessibilityLabelEnabled
&& accessibilityLabelDisabled == equatable.accessibilityLabelDisabled
}
}

View File

@ -11,7 +11,8 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class RadioSwatch: RadioSwatchBase<DefaultRadioSwatchModel>{
@objc(VDSRadioSwatch)
public class RadioSwatch: RadioSwatchBase{
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -21,8 +22,24 @@ public class RadioSwatch: RadioSwatchBase<DefaultRadioSwatchModel>{
}
}
open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
@objc(VDSRadioSwatchBase)
open class RadioSwatchBase: Control, Accessable, DataTrackable, BinaryColorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@ -38,55 +55,38 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
$0.contentMode = .scaleAspectFit
}
}()
open var fillImage: UIImage? { didSet { didChange() }}
open var text: String = "" { didSet { didChange() }}
open var primaryColor: UIColor? { didSet { didChange() }}
open var secondaryColor: UIColor? { didSet { didChange() }}
open var strikethrough: Bool = false { didSet { didChange() }}
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
@Proxy(\.model.text)
open var text: String
@Proxy(\.model.strikethrough)
open var strikethrough: Bool
open var inputId: String? { didSet { didChange() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var value: AnyHashable? { didSet { didChange() }}
open var dataAnalyticsTrack: String? { didSet { didChange() }}
@Proxy(\.model.value)
open var value: AnyHashable?
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var dataClickStream: String? { didSet { didChange() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var dataTrack: String? { didSet { didChange() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { didChange() }}
//functions
//--------------------------------------------------
@ -106,9 +106,7 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
addSubview(selectorView)
selectorView.addSubview(fillView)
updateSelector(model)
selectorView.topAnchor.constraint(equalTo: topAnchor).isActive = true
selectorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
selectorView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
@ -128,7 +126,7 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
public override func reset() {
super.reset()
updateSelector(model)
setNeedsDisplay()
setAccessibilityLabel()
}
@ -140,15 +138,11 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
let enabled = !viewModel.disabled
updateSelector(viewModel)
open override func updateView() {
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setNeedsLayout()
layoutIfNeeded()
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
setNeedsDisplay()
}
//--------------------------------------------------
@ -201,49 +195,6 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
return swatchSize
}
open func updateSelector(_ viewModel: ModelType) {
//get the colors
let backgroundColor = radioSwatchBackgroundColorConfiguration.getColor(viewModel)
let borderColor = viewModel.selected ? radioSwatchBorderColorConfiguration.getColor(viewModel) : .clear
let fillBorderColor = radioSwatchFillBorderColorConfiguration.getColor(viewModel)
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor
selectorView.layer.cornerRadius = selectorView.bounds.width * 0.5
selectorView.layer.borderWidth = viewModel.selected ? selectorBorderWidth : 0
selectorView.layer.masksToBounds = true
gradientLayer?.removeFromSuperlayer()
gradientLayer = nil
var fillColorBackground: UIColor = .clear
if let fillImage = viewModel.fillImage {
fillView.image = viewModel.disabled ? fillImage.image(alpha: disabledAlpha) : fillImage
} else {
fillView.image = nil
if let primary = viewModel.primaryColor, let secondary = viewModel.secondaryColor {
let firstColor = viewModel.disabled ? primary.withAlphaComponent(disabledAlpha) : primary
let secondColor = viewModel.disabled ? secondary.withAlphaComponent(disabledAlpha) : secondary
let gradient = CAGradientLayer()
gradientLayer = gradient
gradient.frame = fillView.bounds
gradient.colors = [secondColor.cgColor, secondColor.cgColor, firstColor.cgColor, firstColor.cgColor]
gradient.locations = [NSNumber(value: 0.0), NSNumber(value: 0.5), NSNumber(value: 0.5), NSNumber(value: 1.0)]
gradient.transform = CATransform3DMakeRotation(135.0 / 180.0 * .pi, 0.0, 0.0, 1.0)
fillView.layer.addSublayer(gradient)
} else {
fillColorBackground = viewModel.primaryColor ?? .white
}
}
fillView.backgroundColor = viewModel.disabled ? fillColorBackground.withAlphaComponent(disabledAlpha) : fillColorBackground
fillView.layer.borderColor = fillBorderColor.cgColor
fillView.layer.cornerRadius = fillView.bounds.width * 0.5
fillView.layer.borderWidth = selectorBorderWidth
fillView.layer.masksToBounds = true
setNeedsDisplay()
}
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any size changes
@ -252,12 +203,51 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
open override func draw(_ layer: CALayer, in ctx: CGContext) {
let borderColor = radioSwatchBorderColorConfiguration.getColor(model)
let backgroundColor = radioSwatchBackgroundColorConfiguration.getColor(self)
let borderColor = isSelected ? radioSwatchBorderColorConfiguration.getColor(self) : .clear
let fillBorderColor = radioSwatchFillBorderColorConfiguration.getColor(self)
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor
selectorView.layer.cornerRadius = selectorView.bounds.width * 0.5
selectorView.layer.borderWidth = isSelected ? selectorBorderWidth : 0
selectorView.layer.masksToBounds = true
gradientLayer?.removeFromSuperlayer()
gradientLayer = nil
var fillColorBackground: UIColor = .clear
if let fillImage {
fillView.image = disabled ? fillImage.image(alpha: disabledAlpha) : fillImage
} else {
fillView.image = nil
if let primary = primaryColor, let secondary = secondaryColor {
let firstColor = disabled ? primary.withAlphaComponent(disabledAlpha) : primary
let secondColor = disabled ? secondary.withAlphaComponent(disabledAlpha) : secondary
let gradient = CAGradientLayer()
gradientLayer = gradient
gradient.frame = fillView.bounds
gradient.colors = [secondColor.cgColor, secondColor.cgColor, firstColor.cgColor, firstColor.cgColor]
gradient.locations = [NSNumber(value: 0.0), NSNumber(value: 0.5), NSNumber(value: 0.5), NSNumber(value: 1.0)]
gradient.transform = CATransform3DMakeRotation(135.0 / 180.0 * .pi, 0.0, 0.0, 1.0)
fillView.layer.addSublayer(gradient)
} else {
fillColorBackground = primaryColor ?? .white
}
}
fillView.backgroundColor = disabled ? fillColorBackground.withAlphaComponent(disabledAlpha) : fillColorBackground
fillView.layer.borderColor = fillBorderColor.cgColor
fillView.layer.cornerRadius = fillView.bounds.width * 0.5
fillView.layer.borderWidth = selectorBorderWidth
fillView.layer.masksToBounds = true
shapeLayer?.removeFromSuperlayer()
shapeLayer = nil
if model.strikethrough {
if strikethrough {
let strikeThroughBorderColor = radioSwatchBorderColorConfiguration.getColor(self)
let bounds = selectorView.bounds
let length = max(bounds.size.height, bounds.size.width)
guard length > 0.0, shapeLayer == nil else { return }
@ -267,7 +257,7 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
strikeThrough.fillColor = nil
strikeThrough.opacity = 1.0
strikeThrough.lineWidth = strikeThroughLineThickness
strikeThrough.strokeColor = borderColor.cgColor
strikeThrough.strokeColor = strikeThroughBorderColor.cgColor
let linePath = UIBezierPath()
linePath.move(to: CGPoint(x: 0, y: bounds.height))

View File

@ -9,29 +9,29 @@ import Foundation
import UIKit
import Combine
public class RadioSwatchGroup: RadioSwatchGroupBase<DefaultRadioSwatchGroupModel, RadioSwatch> {
@objc(VDSRadioSwatchGroup)
public class RadioSwatchGroup: RadioSwatchGroupBase<RadioSwatch> {
public override func didSelect(selector: RadioSwatch) {
if let index = model.selectors.firstIndex(where: {$0.selected == true }),
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? CollectionViewCell<RadioSwatch> {
cell.modelHandler.toggle()
}
selectedHandler?.toggle()
selector.toggle()
label.text = selector.model.text
label.text = selector.text
valueChanged()
}
}
public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHandlerType: RadioSwatchBase<GroupModelType.SelectorModelType>>: Control<GroupModelType>, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
public class RadioSwatchGroupBase<HandlerType: RadioSwatchBase>: SelectorGroupSelectedHandlerBase<HandlerType>, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var selectedModel: ModelHandlerType.ModelType? {
return model.selectedModel
public override var selectorViews: [HandlerType] {
didSet {
collectionView.reloadData()
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -55,7 +55,7 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
$0.showsVerticalScrollIndicator = false
$0.isScrollEnabled = false
$0.translatesAutoresizingMaskIntoConstraints = false
$0.register(CollectionViewCell<ModelHandlerType>.self, forCellWithReuseIdentifier: "collectionViewCell")
$0.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell")
}
}()
@ -64,13 +64,19 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
//--------------------------------------------------
override public var disabled: Bool {
didSet {
updateSelectors()
for selector in selectorViews {
selector.disabled = disabled
}
collectionView.reloadData()
}
}
override public var surface: Surface {
didSet {
updateSelectors()
for selector in selectorViews {
selector.surface = surface
}
collectionView.reloadData()
}
}
@ -109,16 +115,14 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
}
open func setHeight() {
let swatches = model.selectors
guard swatches.count > 0 else {
guard selectorViews.count > 0 else {
collectionViewHeight?.constant = 0
return
}
// Calculate the height
let swatchesInRow = floor(CGFloat(collectionView.bounds.width/(cellSize + itemSpacing)))
let numberOfRows = ceil(CGFloat(swatches.count)/swatchesInRow)
let numberOfRows = ceil(CGFloat(selectorViews.count)/swatchesInRow)
let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1))
collectionViewHeight?.constant = CGFloat(height)
@ -130,29 +134,13 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
collectionView.dataSource = self
}
open override func updateView(viewModel: ModelType) {
label.set(with: viewModel.labelModel)
open override func updateView() {
label.textPosition = .left
label.typograpicalStyle = .BodySmall
label.text = selectedHandler?.text ?? " "
label.surface = surface
label.disabled = disabled
collectionView.reloadData()
setNeedsLayout()
}
//Refactor into new CollectionView Selector protocol
public func updateSelectors(){
let selectors = model.selectors.compactMap { existing in
return existing.copyWith {
$0.disabled = disabled
$0.surface = surface
}
}
model.selectors = selectors
}
public func replace(viewModel: ModelHandlerType.ModelType){
if let index = model.selectors.firstIndex(where: { element in
return element.id == viewModel.id
}) {
model.selectors[index] = viewModel
}
}
//--------------------------------------------------
@ -166,12 +154,11 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
// MARK: - UICollectionViewDelegate
//--------------------------------------------------
open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return !model.selectors[indexPath.row].disabled
return !selectorViews[indexPath.row].disabled
}
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell<ModelHandlerType> else { return }
didSelect(selector: cell.modelHandler)
didSelect(selector: selectorViews[indexPath.row])
}
//--------------------------------------------------
@ -182,53 +169,25 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return model.selectors.count
return selectorViews.count
}
var cellsubs: [Int: AnyCancellable] = [:]
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as? CollectionViewCell<ModelHandlerType> else { return UICollectionViewCell() }
let model = model.selectors[indexPath.row]
cell.modelHandler.isUserInteractionEnabled = false
//cancel if sub exists
if let sub = cellsubs[indexPath.row] {
sub.cancel()
cellsubs[indexPath.row] = nil
}
let sub = cell.modelHandler
.handlerPublisher()
.sink { [weak self] changed in
if cell.modelHandler.shouldUpdateView(viewModel: model) {
print("Model Change: \(changed)")
self?.replace(viewModel: changed)
}
}
cellsubs[indexPath.row] = sub
cell.set(with: model)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath)
let handler = selectorViews[indexPath.row]
handler.isUserInteractionEnabled = false
cell.subviews.forEach { $0.removeFromSuperview() }
cell.addSubview(handler)
handler.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
handler.leadingAnchor.constraint(equalTo: cell.leadingAnchor).isActive = true
handler.trailingAnchor.constraint(equalTo: cell.trailingAnchor).isActive = true
handler.bottomAnchor.constraint(equalTo: cell.bottomAnchor).isActive = true
return cell
}
open func didSelect(selector: ModelHandlerType) {
open func didSelect(selector: HandlerType) {
fatalError("Must override didSelect")
}
public func valueChanged() {
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.ModelStateDebounce) { [weak self] in
self?.sendActions(for: .valueChanged)
}
}
public var selectedModelHandler: ModelHandlerType? {
guard let index = model.selectors.firstIndex(where: {$0.selected == true }),
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? CollectionViewCell<ModelHandlerType> else {
return nil
}
return cell.modelHandler
}
}

View File

@ -1,36 +0,0 @@
//
// RadioSwatchGroupModel.swift
// VDS
//
// Created by Matt Bruce on 8/25/22.
//
import Foundation
public protocol RadioSwatchGroupModel: SelectorGroupSelectedModelable, Equatable where SelectorModelType: RadioSwatchModel { }
extension RadioSwatchGroupModel {
public var labelModel: DefaultLabelModel {
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = selectedModel?.text ?? " "
model.surface = surface
model.disabled = disabled
return model
}
}
public struct DefaultRadioSwatchGroupModel: RadioSwatchGroupModel {
public typealias SelectorModelType = DefaultRadioSwatchModel
public var id = UUID()
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var selectors: [SelectorModelType]
public init() { selectors = [] }
public init(selectors: [SelectorModelType]){
self.selectors = selectors
}
}

View File

@ -1,47 +0,0 @@
//
// RadioSwatchModel.swift
// VDS
//
// Created by Matt Bruce on 8/25/22.
//
import Foundation
import UIKit
public protocol RadioSwatchModel: Modelable, FormFieldable, DataTrackable, Accessable, Selectable, BinaryColorable, Equatable {
var fillImage: UIImage? { get set }
var primaryColor: UIColor? { get set }
var secondaryColor: UIColor? { get set }
var text: String { get set }
var strikethrough: Bool { get set }
}
public struct DefaultRadioSwatchModel: RadioSwatchModel {
public var id = UUID()
public var selected: Bool = false
public var fillImage: UIImage?
public var primaryColor: UIColor?
public var secondaryColor: UIColor?
public var text: String = ""
public var strikethrough: Bool = false
public var inputId: String?
public var value: AnyHashable?
public var surface: Surface = .light
public var disabled: Bool = false
public var dataAnalyticsTrack: String?
public var dataClickStream: String?
public var dataTrack: String?
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public init() {}
}

View File

@ -11,7 +11,25 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
open class EntryField<ModelType: EntryFieldModel>: Control<ModelType> {
public enum HelperTextPlacement: String, CaseIterable {
case bottom, right
}
open class EntryField: Control {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
@ -75,46 +93,32 @@ open class EntryField<ModelType: EntryFieldModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@Proxy(\.model.labelText)
open var labelText: String?
@Proxy(\.model.helperText)
open var helperText: String?
@Proxy(\.model.showError)
open var showError: Bool
@Proxy(\.model.errorText)
open var errorText: String?
@Proxy(\.model.tooltipTitle)
open var tooltipTitle: String?
@Proxy(\.model.tooltipContent)
open var tooltipContent: String?
@Proxy(\.model.transparentBackground)
open var transparentBackground: Bool
@Proxy(\.model.width)
open var width: CGFloat?
@Proxy(\.model.maxLength)
open var maxLength: Int?
@Proxy(\.model.inputId)
open var inputId: String?
@Proxy(\.model.value)
open var value: AnyHashable?
@Proxy(\.model.defaultVaue)
open var defaultValue: AnyHashable?
@Proxy(\.model.required)
open var required: Bool
@Proxy(\.model.readOnly)
open var readOnly: Bool

View File

@ -1,84 +1,84 @@
////
//// EntryFieldModel.swift
//// VDS
////
//// Created by Matt Bruce on 10/3/22.
////
//
// EntryFieldModel.swift
// VDS
//import Foundation
//
// Created by Matt Bruce on 10/3/22.
//public enum HelperTextPlacement: String, CaseIterable {
// case bottom, right
//}
//
import Foundation
public enum HelperTextPlacement: String, CaseIterable {
case bottom, right
}
public protocol EntryFieldModel: Modelable, FormFieldable, Errorable {
var defaultVaue: AnyHashable? { get set }
var required: Bool { get set }
var readOnly: Bool { get set }
var labelText: String? { get set }
var helperText: String? { get set }
var helperTextPlacement: HelperTextPlacement { get set }
var transparentBackground: Bool { get set }
var width: CGFloat? { get set }
var maxLength: Int? { get set }
var tooltipTitle: String? { get set }
var tooltipContent: String? { get set }
}
extension EntryFieldModel {
public var labelModel: DefaultLabelModel {
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = labelText
model.surface = surface
model.disabled = disabled
return model
}
public var helperLabelModel: DefaultLabelModel? {
guard let helperText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = helperText
model.surface = surface
model.disabled = disabled
return model
}
public var errorLabelModel: DefaultLabelModel? {
guard let errorText else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = errorText
model.surface = surface
model.disabled = disabled
return model
}
public var tooltipTitleModel: DefaultLabelModel? {
guard let tooltipTitle else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = tooltipTitle
model.surface = surface
model.disabled = disabled
return model
}
public var tooltipContentModel: DefaultLabelModel? {
guard let tooltipContent else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = tooltipContent
model.surface = surface
model.disabled = disabled
return model
}
}
//public protocol EntryFieldModel: Modelable, FormFieldable, Errorable {
// var defaultVaue: AnyHashable? { get set }
// var required: Bool { get set }
// var readOnly: Bool { get set }
// var labelText: String? { get set }
// var helperText: String? { get set }
// var helperTextPlacement: HelperTextPlacement { get set }
// var transparentBackground: Bool { get set }
// var width: CGFloat? { get set }
// var maxLength: Int? { get set }
// var tooltipTitle: String? { get set }
// var tooltipContent: String? { get set }
//}
//
//extension EntryFieldModel {
//
// public var labelModel: DefaultLabelModel {
// var model = DefaultLabelModel()
// model.textPosition = .left
// model.typograpicalStyle = .BodySmall
// model.text = labelText
// model.surface = surface
// model.disabled = disabled
// return model
// }
//
// public var helperLabelModel: DefaultLabelModel? {
// guard let helperText else { return nil }
// var model = DefaultLabelModel()
// model.textPosition = .left
// model.typograpicalStyle = .BodySmall
// model.text = helperText
// model.surface = surface
// model.disabled = disabled
// return model
// }
//
// public var errorLabelModel: DefaultLabelModel? {
// guard let errorText else { return nil }
// var model = DefaultLabelModel()
// model.textPosition = .left
// model.typograpicalStyle = .BodySmall
// model.text = errorText
// model.surface = surface
// model.disabled = disabled
// return model
// }
//
// public var tooltipTitleModel: DefaultLabelModel? {
// guard let tooltipTitle else { return nil }
// var model = DefaultLabelModel()
// model.textPosition = .left
// model.typograpicalStyle = .BodySmall
// model.text = tooltipTitle
// model.surface = surface
// model.disabled = disabled
// return model
// }
//
// public var tooltipContentModel: DefaultLabelModel? {
// guard let tooltipContent else { return nil }
// var model = DefaultLabelModel()
// model.textPosition = .left
// model.typograpicalStyle = .BodySmall
// model.text = tooltipContent
// model.surface = surface
// model.disabled = disabled
// return model
// }
//
//}

View File

@ -11,10 +11,28 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class TextEntryField: TextEntryFieldBase<DefaultTextEntryField>{}
public enum TextEntryFieldType: String, CaseIterable {
case text, number, calendar, inlineAction, password, creditCard, tel, date, securityCode
}
open class TextEntryFieldBase<ModelType:TextEntryFieldModel>: EntryField<ModelType> {
public class TextEntryField: TextEntryFieldBase{}
open class TextEntryFieldBase: EntryField {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -31,16 +49,12 @@ open class TextEntryFieldBase<ModelType:TextEntryFieldModel>: EntryField<ModelTy
// MARK: - Public Properties
//--------------------------------------------------
@Proxy(\.model.type)
public var type: TextEntryFieldType
@Proxy(\.model.showSuccess)
open var showSuccess: Bool
@Proxy(\.model.successText)
open var successText: String?
@Proxy(\.model.helperTextPlacement)
open var helperTextPlacement: HelperTextPlacement
private var successLabel = Label().with {

View File

@ -1,67 +1,64 @@
////
//// TextEntryFieldModel.swift
//// VDS
////
//// Created by Matt Bruce on 10/3/22.
////
//
// TextEntryFieldModel.swift
// VDS
//import Foundation
//
// Created by Matt Bruce on 10/3/22.
//
import Foundation
public enum TextEntryFieldType: String, CaseIterable {
case text, number, calendar, inlineAction, password, creditCard, tel, date, securityCode
}
public protocol TextEntryFieldModel: EntryFieldModel {
var type: TextEntryFieldType { get set }
var showSuccess: Bool { get set }
var successText: String? { get set }
}
extension TextEntryFieldModel {
public var successLabelModel: DefaultLabelModel? {
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodySmall
model.text = successText
model.surface = surface
model.disabled = disabled
return model
}
}
public struct DefaultTextEntryField: TextEntryFieldModel {
public var id = UUID()
public var inputId: String?
public var type: TextEntryFieldType = .text
public var value: AnyHashable?
public var defaultVaue: AnyHashable?
public var required: Bool = false
public var readOnly: Bool = false
public var labelText: String?
public var helperText: String?
public var helperTextPlacement: HelperTextPlacement = .bottom
public var showError: Bool = false
public var errorText: String?
public var showSuccess: Bool = false
public var successText: String?
public var transparentBackground: Bool = false
public var width: CGFloat?
public var maxLength: Int?
public var tooltipTitle: String?
public var tooltipContent: String?
public var surface: Surface = .light
public var disabled: Bool = false
public init() { }
}
//public protocol TextEntryFieldModel: EntryFieldModel {
// var type: TextEntryFieldType { get set }
// var showSuccess: Bool { get set }
// var successText: String? { get set }
//}
//
//extension TextEntryFieldModel {
//
// public var successLabelModel: DefaultLabelModel? {
// var model = DefaultLabelModel()
// model.textPosition = .left
// model.typograpicalStyle = .BodySmall
// model.text = successText
// model.surface = surface
// model.disabled = disabled
// return model
// }
//
//}
//
//
//public struct DefaultTextEntryField: TextEntryFieldModel {
// public var id = UUID()
// public var inputId: String?
//
// public var type: TextEntryFieldType = .text
// public var value: AnyHashable?
// public var defaultVaue: AnyHashable?
// public var required: Bool = false
// public var readOnly: Bool = false
//
// public var labelText: String?
//
// public var helperText: String?
// public var helperTextPlacement: HelperTextPlacement = .bottom
//
// public var showError: Bool = false
// public var errorText: String?
//
// public var showSuccess: Bool = false
// public var successText: String?
//
// public var transparentBackground: Bool = false
// public var width: CGFloat?
// public var maxLength: Int?
//
// public var tooltipTitle: String?
// public var tooltipContent: String?
//
// public var surface: Surface = .light
// public var disabled: Bool = false
//
// public init() { }
//}

View File

@ -9,6 +9,19 @@ import Foundation
import UIKit
import VDSColorTokens
import Combine
public enum ToggleTextSize: String, CaseIterable {
case small, large
}
public enum ToggleTextWeight: String, CaseIterable {
case regular, bold
}
public enum ToggleTextPosition: String, CaseIterable {
case left, right
}
/**
A custom implementation of Apple's UISwitch.
@ -17,8 +30,8 @@ import Combine
Container: The background of the toggle control.
Knob: The circular indicator that slides on the container.
*/
public class Toggle: ToggleBase<DefaultToggleModel>{
@objc(VDSToggle)
public class Toggle: ToggleBase{
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -28,7 +41,23 @@ public class Toggle: ToggleBase<DefaultToggleModel>{
}
}
open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
@objc(VDSToggleBase)
open class ToggleBase: Control, Accessable, DataTrackable, BinaryColorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
@ -86,136 +115,93 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
$0.forFalse.disabled.darkColor = VDSColor.paletteGray44
}
private var typograpicalStyle: TypographicalStyle {
if textSize == .small {
if textWeight == .bold {
return .BoldBodySmall
} else {
return .BodySmall
}
} else {
if textWeight == .bold {
return .BoldBodyLarge
} else {
return .BodyLarge
}
}
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@Proxy(\.model.on)
open var isOn: Bool
open var isOn: Bool = false { didSet { didChange() }}
open var isAnimated: Bool = true { didSet { didChange() }}
@Proxy(\.model.showText)
public var showText: Bool {
didSet {
if oldValue != showText {
updateLabel(model)
}
}
}
open var showText: Bool = false { didSet { didChange() }}
@Proxy(\.model.onText)
public var onText: String
open var onText: String = "On" { didSet { didChange() }}
@Proxy(\.model.offText)
public var offText: String
open var offText: String = "Off" { didSet { didChange() }}
public var fontSize: TypographicalStyle.FontSize = .small{
didSet {
updateTypography()
}
}
open var textSize: ToggleTextSize = .small { didSet { didChange() }}
public var isBold: Bool = false {
didSet {
updateTypography()
}
}
open var textWeight: ToggleTextWeight = .regular { didSet { didChange() }}
@Proxy(\.model.textPosition)
public var textPosition: TextPosition {
didSet {
if oldValue != textPosition {
updateLabel(model)
}
}
}
open var textPosition: ToggleTextPosition = .left { didSet { didChange() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var inputId: String? { didSet { didChange() }}
@Proxy(\.model.value)
open var value: AnyHashable?
open var value: AnyHashable? { didSet { didChange() }}
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var dataAnalyticsTrack: String? { didSet { didChange() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var dataClickStream: String? { didSet { didChange() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var dataTrack: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { didChange() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { didChange() }}
//only allows
//fontSize: small, large
//fontWeight: regular, bold
private func updateTypography() {
if fontSize == .small {
if isBold {
model.typograpicalStyle = .BoldBodySmall
} else {
model.typograpicalStyle = .BodySmall
}
} else {
if isBold {
model.typograpicalStyle = .BoldBodyLarge
} else {
model.typograpicalStyle = .BodyLarge
}
}
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var knobLeadingConstraint: NSLayoutConstraint?
private var knobTrailingConstraint: NSLayoutConstraint?
private var knobHeightConstraint: NSLayoutConstraint?
private var knobWidthConstraint: NSLayoutConstraint?
private var toggleHeightConstraint: NSLayoutConstraint?
private var toggleWidthConstraint: NSLayoutConstraint?
//functions
//--------------------------------------------------
// MARK: - Toggle
//--------------------------------------------------
private func updateToggle(_ viewModel: ModelType) {
//private func
func constrainKnob(){
self.knobLeadingConstraint?.isActive = false
self.knobTrailingConstraint?.isActive = false
if viewModel.on {
self.knobTrailingConstraint = self.toggleView.trailingAnchor.constraint(equalTo: self.knobView.trailingAnchor, constant: 2)
self.knobLeadingConstraint = self.knobView.leadingAnchor.constraint(greaterThanOrEqualTo: self.toggleView.leadingAnchor)
} else {
self.knobTrailingConstraint = self.toggleView.trailingAnchor.constraint(greaterThanOrEqualTo: self.knobView.trailingAnchor)
self.knobLeadingConstraint = self.knobView.leadingAnchor.constraint(equalTo: self.toggleView.leadingAnchor, constant: 2)
}
self.knobTrailingConstraint?.isActive = true
self.knobLeadingConstraint?.isActive = true
self.knobWidthConstraint?.constant = self.knobSize.width
self.layoutIfNeeded()
private func constrainKnob(){
self.knobLeadingConstraint?.isActive = false
self.knobTrailingConstraint?.isActive = false
if isOn {
knobTrailingConstraint = toggleView.trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 2)
knobLeadingConstraint = knobView.leadingAnchor.constraint(greaterThanOrEqualTo: toggleView.leadingAnchor)
} else {
knobTrailingConstraint = toggleView.trailingAnchor.constraint(greaterThanOrEqualTo: knobView.trailingAnchor)
knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: toggleView.leadingAnchor, constant: 2)
}
knobTrailingConstraint?.isActive = true
knobLeadingConstraint?.isActive = true
self.layoutIfNeeded()
}
private func updateToggle() {
let toggleColor = toggleColorConfiguration.getColor(self)
let knobColor = knobColorConfiguration.getColor(self)
let toggleColor = toggleColorConfiguration.getColor(viewModel)
let knobColor = knobColorConfiguration.getColor(viewModel)
if viewModel.disabled {
if disabled || !isAnimated {
toggleView.backgroundColor = toggleColor
knobView.backgroundColor = knobColor
constrainKnob()
@ -225,8 +211,8 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
self.knobView.backgroundColor = knobColor
}, completion: nil)
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [], animations: {
constrainKnob()
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [], animations: { [weak self] in
self?.constrainKnob()
}, completion: nil)
}
}
@ -234,8 +220,8 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - Labels
//--------------------------------------------------
private func updateLabel(_ viewModel: ModelType) {
let showText = viewModel.showText
private func updateLabel() {
stackView.spacing = showText ? 12 : 0
if stackView.subviews.contains(label) {
@ -243,6 +229,12 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
}
if showText {
label.textPosition = textPosition == .left ? .left : .right
label.typograpicalStyle = typograpicalStyle
label.text = isOn ? onText : offText
label.surface = surface
label.disabled = disabled
if textPosition == .left {
stackView.insertArrangedSubview(label, at: 0)
} else {
@ -265,75 +257,68 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
isAccessibilityElement = true
accessibilityTraits = .button
addSubview(stackView)
//create the wrapping view
//set the h/w to container size, since the width "can" grow if text is there
//allow this to be greaterThanEqualTo
heightAnchor.constraint(equalToConstant: toggleContainerSize.height).isActive = true
widthAnchor.constraint(greaterThanOrEqualToConstant: toggleContainerSize.width).isActive = true
//create the container for the toggle/knob
let toggleContainerView = UIView()
toggleContainerView.translatesAutoresizingMaskIntoConstraints = false
toggleContainerView.backgroundColor = .clear
toggleContainerView.widthAnchor.constraint(equalToConstant: toggleContainerSize.width).isActive = true
toggleContainerView.heightAnchor.constraint(equalToConstant: toggleContainerSize.height).isActive = true
toggleHeightConstraint = toggleView.heightAnchor.constraint(equalToConstant: toggleSize.height)
toggleHeightConstraint?.isActive = true
toggleWidthConstraint = toggleView.widthAnchor.constraint(equalToConstant: toggleSize.width)
toggleWidthConstraint?.isActive = true
toggleView.layer.cornerRadius = toggleSize.height / 2.0
knobView.layer.cornerRadius = knobSize.height / 2.0
toggleView.backgroundColor = toggleColorConfiguration.getColor(model)
//adding views
toggleContainerView.addSubview(toggleView)
toggleView.addSubview(knobView)
knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: knobSize.height)
knobHeightConstraint?.isActive = true
knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: knobSize.width)
knobWidthConstraint?.isActive = true
stackView.addArrangedSubview(toggleContainerView)
//adding constraints
toggleView.heightAnchor.constraint(equalToConstant: toggleSize.height).isActive = true
toggleView.widthAnchor.constraint(equalToConstant: toggleSize.width).isActive = true
toggleView.layer.cornerRadius = toggleSize.height / 2.0
toggleView.bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true
toggleView.centerXAnchor.constraint(equalTo: toggleContainerView.centerXAnchor).isActive = true
toggleView.centerYAnchor.constraint(equalTo: toggleContainerView.centerYAnchor).isActive = true
knobView.layer.cornerRadius = knobSize.height / 2.0
knobView.heightAnchor.constraint(equalToConstant: knobSize.height).isActive = true
knobView.widthAnchor.constraint(equalToConstant: knobSize.width).isActive = true
knobView.centerYAnchor.constraint(equalTo: toggleView.centerYAnchor).isActive = true
knobView.topAnchor.constraint(greaterThanOrEqualTo: toggleView.topAnchor).isActive = true
toggleView.bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true
updateLabel(model)
stackView.addArrangedSubview(toggleContainerView)
//pin stackview to edges
stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
toggleView.centerXAnchor.constraint(equalTo: toggleContainerView.centerXAnchor).isActive = true
toggleView.centerYAnchor.constraint(equalTo: toggleContainerView.centerYAnchor).isActive = true
}
public override func reset() {
super.reset()
toggleView.backgroundColor = toggleColorConfiguration.getColor(model)
knobView.backgroundColor = knobColorConfiguration.getColor(model)
toggleView.backgroundColor = toggleColorConfiguration.getColor(self)
knobView.backgroundColor = knobColorConfiguration.getColor(self)
setAccessibilityLabel()
}
/// This will toggle the state of the Toggle and execute the actionBlock if provided.
open func toggle() {
isOn.toggle()
isSelected = isOn
sendActions(for: .valueChanged)
}
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
label.set(with: viewModel.label)
updateLabel(viewModel)
updateToggle(viewModel)
open override func updateView() {
updateLabel()
updateToggle()
setAccessibilityHint()
setAccessibilityValue(viewModel.on)
setAccessibilityLabel(viewModel.on)
backgroundColor = viewModel.surface.color
setNeedsLayout()
layoutIfNeeded()
setAccessibilityValue(isOn)
setAccessibilityLabel(isOn)
backgroundColor = surface.color
}
}

View File

@ -1,59 +0,0 @@
//
// ToggleModel.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
//
import Foundation
import UIKit
public protocol ToggleModel: Modelable, FormFieldable, DataTrackable, Accessable, Labelable, BinaryColorable {
var showText: Bool { get set }
var on: Bool { get set }
var offText: String { get set }
var onText: String { get set }
}
extension ToggleModel {
public var userTrueColor: Bool { return on }
public var label: DefaultLabelModel {
var model = DefaultLabelModel()
model.textPosition = textPosition
model.typograpicalStyle = typograpicalStyle
model.text = on ? onText : offText
model.surface = surface
model.disabled = disabled
return model
}
}
public struct DefaultToggleModel: ToggleModel {
public var id = UUID()
public var on: Bool = false
public var showText: Bool = false
public var offText: String = "Off"
public var onText: String = "On"
public var typograpicalStyle: TypographicalStyle = .BodySmall
public var textPosition: TextPosition = .left
public var inputId: String?
public var value: AnyHashable? = true
public var surface: Surface = .light
public var disabled: Bool = false
public var dataAnalyticsTrack: String?
public var dataClickStream: String?
public var dataTrack: String?
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public init() { }
}

View File

@ -1,45 +0,0 @@
//
// ProxPropertyWrapper.swift
// VDS
//
// Created by Matt Bruce on 7/29/22.
//
import Foundation
//https://www.swiftbysundell.com/articles/accessing-a-swift-property-wrappers-enclosing-instance/
@propertyWrapper
public struct AnyProxy<EnclosingSelf, Value> {
private let keyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>
public init(_ keyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>) {
self.keyPath = keyPath
}
@available(*, unavailable, message: "The wrapped value must be accessed from the enclosing instance property.")
public var wrappedValue: Value {
get { fatalError() }
set { fatalError() }
}
public static subscript(
_enclosingInstance observed: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
) -> Value {
get {
let storageValue = observed[keyPath: storageKeyPath]
let value = observed[keyPath: storageValue.keyPath]
return value
}
set {
let storageValue = observed[keyPath: storageKeyPath]
observed[keyPath: storageValue.keyPath] = newValue
}
}
}
extension NSObject: ProxyContainer {}
public protocol ProxyContainer {
typealias Proxy<T> = AnyProxy<Self, T>
}

View File

@ -1,57 +0,0 @@
//
// CodableColor.swift
// VDS
//
// Created by Matt Bruce on 8/26/22.
//
import Foundation
import UIKit
@propertyWrapper
public struct CodableColor {
public var wrappedValue: UIColor
public init(wrappedValue: UIColor) {
self.wrappedValue = wrappedValue
}
}
extension CodableColor: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let colorString = try container.decode(String.self)
wrappedValue = UIColor(hexString: colorString)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue.hexString)
}
}
@propertyWrapper
public struct OptionalCodableColor {
public var wrappedValue: UIColor?
public init(wrappedValue: UIColor?) {
self.wrappedValue = wrappedValue
}
}
extension OptionalCodableColor: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
wrappedValue = nil
} else {
let colorString = try container.decode(String.self)
wrappedValue = UIColor(hexString: colorString)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue?.hexString)
}
}

View File

@ -1,31 +0,0 @@
//
// File.swift
// VDS
//
// Created by Matt Bruce on 8/11/22.
//
import Foundation
@propertyWrapper
public struct Debuggable<Value> {
private var value: Value
private let description: String
public init(wrappedValue: Value, description: String = "") {
print("Initialized '\(description)' with value \(wrappedValue)")
self.value = wrappedValue
self.description = description
}
public var wrappedValue: Value {
get {
print("Accessing '\(description)', returning: \(value)")
return value
}
set {
print("Updating '\(description)', newValue: \(newValue)")
value = newValue
}
}
}

View File

@ -1,23 +0,0 @@
//
// UseAutoLayout.swift
// VDS
//
// Created by Matt Bruce on 8/11/22.
//
import Foundation
import UIKit
@propertyWrapper
public struct UsesAutoLayout<T: UIView> {
public var wrappedValue: T {
didSet {
wrappedValue.translatesAutoresizingMaskIntoConstraints = false
}
}
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
wrappedValue.translatesAutoresizingMaskIntoConstraints = false
}
}

View File

@ -18,37 +18,31 @@ public protocol Accessable {
}
//Configurations to set within the UIControl
extension ModelHandlerable where Self: UIView {
private var accessableModel: Accessable? {
guard let model = self.model as? Accessable else {
return nil
}
return model
}
extension Handlerable where Self: UIView, Self: Accessable {
public func setAccessibilityHint(_ override: Bool? = nil) {
let check = override ?? !model.disabled
if let value = accessableModel?.accessibilityHintEnabled, check {
let check = override ?? !disabled
if let value = accessibilityHintEnabled, check {
accessibilityHint = value
} else if let value = accessableModel?.accessibilityHintDisabled, !check {
} else if let value = accessibilityHintDisabled, !check {
accessibilityHint = value
}
}
public func setAccessibilityValue(_ override: Bool? = nil) {
let check = override ?? !model.disabled
if let value = accessableModel?.accessibilityValueEnabled, check {
let check = override ?? !disabled
if let value = accessibilityValueEnabled, check {
accessibilityValue = value
} else if let value = accessableModel?.accessibilityValueDisabled, !check {
} else if let value = accessibilityValueDisabled, !check {
accessibilityValue = value
}
}
public func setAccessibilityLabel(_ override: Bool? = nil) {
let check = override ?? !model.disabled
if let value = accessableModel?.accessibilityLabelEnabled, check {
let check = override ?? !disabled
if let value = accessibilityLabelEnabled, check {
accessibilityLabel = value
} else if let value = accessableModel?.accessibilityLabelDisabled, !check {
} else if let value = accessibilityLabelDisabled, !check {
accessibilityLabel = value
}
}

View File

@ -8,15 +8,15 @@
import Foundation
import UIKit
public protocol Colorable<ModelType> {
associatedtype ModelType
func getColor(_ viewModel: ModelType) -> UIColor
public protocol Colorable<ObjectType> {
associatedtype ObjectType
func getColor(_ object: ObjectType) -> UIColor
}
extension Colorable {
fileprivate func getColor(_ viewModel: Any) -> UIColor {
guard let model = viewModel as? ModelType else {
assertionFailure("Invalid ModelType, Expecting type \(ModelType.self), received \(viewModel) ")
fileprivate func getColor(_ object: Any) -> UIColor {
guard let model = object as? ObjectType else {
assertionFailure("Invalid ObjectType, Expecting type \(ObjectType.self), received \(object) ")
return .black
}
return getColor(model)
@ -25,15 +25,15 @@ extension Colorable {
public func eraseToAnyColorable() -> AnyColorable { AnyColorable(self) }
}
public struct GenericColorable<ModelType>: Colorable, Withable {
private var wrapped: any Colorable<ModelType>
public struct GenericColorable<ObjectType>: Colorable, Withable {
private var wrapped: any Colorable<ObjectType>
public init<C: Colorable>(colorable: C) where C.ModelType == ModelType {
public init<C: Colorable>(colorable: C) where C.ObjectType == ObjectType {
wrapped = colorable
}
public func getColor(_ viewModel: ModelType) -> UIColor {
wrapped.getColor(viewModel)
public func getColor(_ object: ObjectType) -> UIColor {
wrapped.getColor(object)
}
}
@ -44,7 +44,7 @@ public struct AnyColorable: Colorable, Withable {
wrapped = colorable
}
public func getColor(_ viewModel: Any) -> UIColor {
wrapped.getColor(viewModel)
public func getColor(_ object: Any) -> UIColor {
wrapped.getColor(object)
}
}

View File

@ -0,0 +1,33 @@
//
// Handlerable.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
//
import Foundation
import Combine
import UIKit
public protocol Handlerable: AnyObject, Initable, Disabling, Surfaceable {
var subject: PassthroughSubject<Void, Never> { get set }
var subscribers: Set<AnyCancellable> { get set }
func updateView()
}
extension Handlerable {
public func handlerPublisher() -> AnyPublisher<Void, Never> {
subject
.eraseToAnyPublisher()
.debounce(for: .seconds(Constants.StateDebounce), scheduler: RunLoop.main)
.eraseToAnyPublisher()
}
}
extension Handlerable where Self: UIView {
public func didChange() {
subject.send()
setNeedsLayout()
}
}

View File

@ -1,20 +0,0 @@
//
// LabelModelable.swift
// VDS
//
// Created by Matt Bruce on 7/28/22.
//
import Foundation
import UIKit
public protocol Labelable {
var typograpicalStyle: TypographicalStyle { get set }
var textPosition: TextPosition { get set }
}
extension Labelable {
public var font: UIFont? {
return typograpicalStyle.font
}
}

View File

@ -1,53 +0,0 @@
//
// Modelable.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
//
import Foundation
import Combine
import UIKit
public protocol ModelHandlerable: AnyObject, Initable {
associatedtype ModelType: Modelable
var model: ModelType { get set }
var modelPublisher: Published<ModelType>.Publisher { get }
var subscribers: Set<AnyCancellable> { get set }
init(with model: ModelType)
func set(with model: ModelType)
func shouldUpdateView(viewModel: ModelType) -> Bool
func updateView(viewModel: ModelType)
}
extension ModelHandlerable {
public init() {
self.init(with: ModelType())
}
public func set(with model: ModelType) {
if shouldUpdateView(viewModel: model){
updateView(viewModel: model)
self.model = model
}
}
public func shouldUpdateView(viewModel: ModelType) -> Bool {
model != viewModel
}
public func setupUpdateView() {
handlerPublisher()
.subscribe(on: RunLoop.main)
.sink { [weak self] viewModel in
self?.updateView(viewModel: viewModel)
}
.store(in: &subscribers)
}
public func handlerPublisher() -> AnyPublisher<ModelType, Never> {
modelPublisher
.debounce(for: .seconds(Constants.ModelStateDebounce), scheduler: RunLoop.main)
.eraseToAnyPublisher()
}
}

View File

@ -1,18 +0,0 @@
//
// Modelable.swift
// VDS
//
// Created by Matt Bruce on 8/5/22.
//
import Foundation
public protocol Modelable: Surfaceable, Disabling, Initable, Withable, Identifiable, Equatable, CustomDebugStringConvertible where ID == UUID {
}
extension Modelable {
public var debugDescription: String {
return "\(id.uuidString)"
}
}

View File

@ -1,12 +0,0 @@
//
// Selectable.swift
// VDS
//
// Created by Matt Bruce on 8/23/22.
//
import Foundation
public protocol Selectable {
var selected: Bool { get set }
}

View File

@ -1,37 +0,0 @@
//
// SelectorGroupModel.swift
// VDS
//
// Created by Matt Bruce on 8/11/22.
//
import Foundation
///MARK: Groups that allow anything selected
public protocol SelectorGroupModelable: Modelable, FormFieldable {
associatedtype SelectorModelType: Modelable where SelectorModelType: FormFieldable & Selectable
var selectors: [SelectorModelType] { get set }
}
extension SelectorGroupModelable {
public var debugDescription: String {
"group id: \(id.uuidString)\r\(selectors.compactMap{"id: \($0.debugDescription) \($0.selected ? "*" : "")"}.joined(separator: "\r"))"
}
}
///MARK: Groups that allow single selections
public protocol SelectorGroupSelectedModelable: SelectorGroupModelable {
var selectedModel: SelectorModelType? { get }
}
extension SelectorGroupSelectedModelable {
public var selectedModel: SelectorModelType? {
if let index = selectors.firstIndex(where: { element in
return element.selected == true
}) {
return selectors[index]
} else {
return nil
}
}
}