├── .gitignore ├── .swift-version ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .travis.yml ├── Example ├── Podfile ├── Podfile.lock ├── PrefsMate.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── PrefsMate-Example.xcscheme ├── PrefsMate.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── PrefsMate │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Prefs.plist │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── PrefsViewController.swift │ └── zh-Hans.lproj │ │ └── Prefs.plist └── Tests │ ├── Info.plist │ └── Tests.swift ├── LICENSE ├── Package.swift ├── PrefsMate.podspec ├── README.md └── Source ├── Pref.swift ├── PrefsMate.swift ├── PrefsSupportable.swift ├── PrefsTableViewCell+Switch.swift ├── PrefsTableViewCell.swift └── TransferHelper.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | Pods/ 6 | _Pods.xcodeproj 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata/ 17 | *.xccheckout 18 | profile 19 | *.moved-aside 20 | DerivedData 21 | *.hmap 22 | *.ipa 23 | 24 | # Bundler 25 | .bundle 26 | 27 | Carthage 28 | # We recommend against adding the Pods directory to your .gitignore. However 29 | # you should judge for yourself, the pros and cons are mentioned at: 30 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 31 | # 32 | # Note: if you ignore the Pods directory, make sure to uncomment 33 | # `pod install` in .travis.yml 34 | # 35 | # Pods/ 36 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: swift 6 | osx_image: xcode10.2 7 | sudo: required 8 | env: 9 | global: 10 | - WORKSPACE=Example/PrefsMate.xcworkspace 11 | - SCHEME=PrefsMate-Example 12 | - SDK=iphonesimulator12.2 13 | matrix: 14 | - DESTINATION="platform=iOS Simulator,name=iPhone SE,OS=12.2" 15 | podfile: Example/Podfile 16 | before_script: 17 | - swift --version 18 | script: 19 | - set -o pipefail && xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c 20 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'PrefsMate_Example' do 4 | pod 'PrefsMate', :path => '../' 5 | 6 | target 'PrefsMate_Tests' do 7 | inherit! :search_paths 8 | 9 | 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - PrefsMate (0.3.5) 3 | 4 | DEPENDENCIES: 5 | - PrefsMate (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | PrefsMate: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | PrefsMate: 6458abd881b3792199d56c0c158755fdf0fbf9a8 13 | 14 | PODFILE CHECKSUM: 660ca1154d42b2ab5f352aab5bb790c899d5784b 15 | 16 | COCOAPODS: 1.8.4 17 | -------------------------------------------------------------------------------- /Example/PrefsMate.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1379B6DFC87C31CD9D31EA66 /* Pods_PrefsMate_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8C488F0C34416B60EB4C36B /* Pods_PrefsMate_Tests.framework */; }; 11 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 12 | 607FACD81AFB9204008FA782 /* PrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* PrefsViewController.swift */; }; 13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 14 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 15 | 6775A06C1F99FB4600D05CC3 /* Prefs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6775A06E1F99FB4600D05CC3 /* Prefs.plist */; }; 16 | E1E47DC80B3B940E83A6413F /* Pods_PrefsMate_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1B69BE5DD024DE996DD569F /* Pods_PrefsMate_Example.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 25 | remoteInfo = PrefsMate; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 06CD48987BDBCA6120281244 /* Pods-PrefsMate_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PrefsMate_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PrefsMate_Tests/Pods-PrefsMate_Tests.release.xcconfig"; sourceTree = ""; }; 31 | 22E3A4F8DA35D1097AB91CBA /* PrefsMate.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = PrefsMate.podspec; path = ../PrefsMate.podspec; sourceTree = ""; }; 32 | 607FACD01AFB9204008FA782 /* PrefsMate_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PrefsMate_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | 607FACD71AFB9204008FA782 /* PrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsViewController.swift; sourceTree = ""; }; 36 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 37 | 607FACE51AFB9204008FA782 /* PrefsMate_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PrefsMate_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 40 | 6775A06D1F99FB4600D05CC3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Base; path = Base.lproj/Prefs.plist; sourceTree = ""; }; 41 | 6775A06F1F99FB5700D05CC3 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hans"; path = "zh-Hans.lproj/Prefs.plist"; sourceTree = ""; }; 42 | 7599586AF533BC1B8FE7F1AF /* Pods-PrefsMate_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PrefsMate_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-PrefsMate_Example/Pods-PrefsMate_Example.release.xcconfig"; sourceTree = ""; }; 43 | 95F35D04A7209424C1FD5BD8 /* Pods-PrefsMate_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PrefsMate_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PrefsMate_Example/Pods-PrefsMate_Example.debug.xcconfig"; sourceTree = ""; }; 44 | A1B69BE5DD024DE996DD569F /* Pods_PrefsMate_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PrefsMate_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | AA03F62075DC19E7C5209CB4 /* Pods-PrefsMate_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PrefsMate_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PrefsMate_Tests/Pods-PrefsMate_Tests.debug.xcconfig"; sourceTree = ""; }; 46 | BE2A9908407F218BCA23BE16 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 47 | CD5A4803DF31107049DC5DD8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 48 | D8C488F0C34416B60EB4C36B /* Pods_PrefsMate_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PrefsMate_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | E1E47DC80B3B940E83A6413F /* Pods_PrefsMate_Example.framework in Frameworks */, 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 1379B6DFC87C31CD9D31EA66 /* Pods_PrefsMate_Tests.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 607FACC71AFB9204008FA782 = { 72 | isa = PBXGroup; 73 | children = ( 74 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 75 | 607FACD21AFB9204008FA782 /* Example for PrefsMate */, 76 | 607FACE81AFB9204008FA782 /* Tests */, 77 | 607FACD11AFB9204008FA782 /* Products */, 78 | 85208970E017BBCE635E83CA /* Pods */, 79 | E342A186B2C279E14E46F699 /* Frameworks */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 607FACD11AFB9204008FA782 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 607FACD01AFB9204008FA782 /* PrefsMate_Example.app */, 87 | 607FACE51AFB9204008FA782 /* PrefsMate_Tests.xctest */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | 607FACD21AFB9204008FA782 /* Example for PrefsMate */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 96 | 607FACD71AFB9204008FA782 /* PrefsViewController.swift */, 97 | 6775A06E1F99FB4600D05CC3 /* Prefs.plist */, 98 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 99 | 607FACD31AFB9204008FA782 /* Supporting Files */, 100 | ); 101 | name = "Example for PrefsMate"; 102 | path = PrefsMate; 103 | sourceTree = ""; 104 | }; 105 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 607FACD41AFB9204008FA782 /* Info.plist */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | 607FACE81AFB9204008FA782 /* Tests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 117 | 607FACE91AFB9204008FA782 /* Supporting Files */, 118 | ); 119 | path = Tests; 120 | sourceTree = ""; 121 | }; 122 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 607FACEA1AFB9204008FA782 /* Info.plist */, 126 | ); 127 | name = "Supporting Files"; 128 | sourceTree = ""; 129 | }; 130 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 22E3A4F8DA35D1097AB91CBA /* PrefsMate.podspec */, 134 | CD5A4803DF31107049DC5DD8 /* README.md */, 135 | BE2A9908407F218BCA23BE16 /* LICENSE */, 136 | ); 137 | name = "Podspec Metadata"; 138 | sourceTree = ""; 139 | }; 140 | 85208970E017BBCE635E83CA /* Pods */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 95F35D04A7209424C1FD5BD8 /* Pods-PrefsMate_Example.debug.xcconfig */, 144 | 7599586AF533BC1B8FE7F1AF /* Pods-PrefsMate_Example.release.xcconfig */, 145 | AA03F62075DC19E7C5209CB4 /* Pods-PrefsMate_Tests.debug.xcconfig */, 146 | 06CD48987BDBCA6120281244 /* Pods-PrefsMate_Tests.release.xcconfig */, 147 | ); 148 | name = Pods; 149 | sourceTree = ""; 150 | }; 151 | E342A186B2C279E14E46F699 /* Frameworks */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | A1B69BE5DD024DE996DD569F /* Pods_PrefsMate_Example.framework */, 155 | D8C488F0C34416B60EB4C36B /* Pods_PrefsMate_Tests.framework */, 156 | ); 157 | name = Frameworks; 158 | sourceTree = ""; 159 | }; 160 | /* End PBXGroup section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | 607FACCF1AFB9204008FA782 /* PrefsMate_Example */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "PrefsMate_Example" */; 166 | buildPhases = ( 167 | 4A1F8008AE90F44497B0A2AE /* [CP] Check Pods Manifest.lock */, 168 | 607FACCC1AFB9204008FA782 /* Sources */, 169 | 607FACCD1AFB9204008FA782 /* Frameworks */, 170 | 607FACCE1AFB9204008FA782 /* Resources */, 171 | 9DEE63E78614C42D370F07F2 /* [CP] Embed Pods Frameworks */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | ); 177 | name = PrefsMate_Example; 178 | productName = PrefsMate; 179 | productReference = 607FACD01AFB9204008FA782 /* PrefsMate_Example.app */; 180 | productType = "com.apple.product-type.application"; 181 | }; 182 | 607FACE41AFB9204008FA782 /* PrefsMate_Tests */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "PrefsMate_Tests" */; 185 | buildPhases = ( 186 | CBDEB373E12112C39AE06F4D /* [CP] Check Pods Manifest.lock */, 187 | 607FACE11AFB9204008FA782 /* Sources */, 188 | 607FACE21AFB9204008FA782 /* Frameworks */, 189 | 607FACE31AFB9204008FA782 /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 195 | ); 196 | name = PrefsMate_Tests; 197 | productName = Tests; 198 | productReference = 607FACE51AFB9204008FA782 /* PrefsMate_Tests.xctest */; 199 | productType = "com.apple.product-type.bundle.unit-test"; 200 | }; 201 | /* End PBXNativeTarget section */ 202 | 203 | /* Begin PBXProject section */ 204 | 607FACC81AFB9204008FA782 /* Project object */ = { 205 | isa = PBXProject; 206 | attributes = { 207 | LastSwiftUpdateCheck = 0720; 208 | LastUpgradeCheck = 0820; 209 | ORGANIZATIONNAME = CocoaPods; 210 | TargetAttributes = { 211 | 607FACCF1AFB9204008FA782 = { 212 | CreatedOnToolsVersion = 6.3.1; 213 | LastSwiftMigration = 0900; 214 | }; 215 | 607FACE41AFB9204008FA782 = { 216 | CreatedOnToolsVersion = 6.3.1; 217 | LastSwiftMigration = 0900; 218 | TestTargetID = 607FACCF1AFB9204008FA782; 219 | }; 220 | }; 221 | }; 222 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "PrefsMate" */; 223 | compatibilityVersion = "Xcode 3.2"; 224 | developmentRegion = English; 225 | hasScannedForEncodings = 0; 226 | knownRegions = ( 227 | English, 228 | Base, 229 | "zh-Hans", 230 | ); 231 | mainGroup = 607FACC71AFB9204008FA782; 232 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 233 | projectDirPath = ""; 234 | projectRoot = ""; 235 | targets = ( 236 | 607FACCF1AFB9204008FA782 /* PrefsMate_Example */, 237 | 607FACE41AFB9204008FA782 /* PrefsMate_Tests */, 238 | ); 239 | }; 240 | /* End PBXProject section */ 241 | 242 | /* Begin PBXResourcesBuildPhase section */ 243 | 607FACCE1AFB9204008FA782 /* Resources */ = { 244 | isa = PBXResourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 248 | 6775A06C1F99FB4600D05CC3 /* Prefs.plist in Resources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | 607FACE31AFB9204008FA782 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXResourcesBuildPhase section */ 260 | 261 | /* Begin PBXShellScriptBuildPhase section */ 262 | 4A1F8008AE90F44497B0A2AE /* [CP] Check Pods Manifest.lock */ = { 263 | isa = PBXShellScriptBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | ); 267 | inputPaths = ( 268 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 269 | "${PODS_ROOT}/Manifest.lock", 270 | ); 271 | name = "[CP] Check Pods Manifest.lock"; 272 | outputPaths = ( 273 | "$(DERIVED_FILE_DIR)/Pods-PrefsMate_Example-checkManifestLockResult.txt", 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | shellPath = /bin/sh; 277 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 278 | showEnvVarsInLog = 0; 279 | }; 280 | 9DEE63E78614C42D370F07F2 /* [CP] Embed Pods Frameworks */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | "${PODS_ROOT}/Target Support Files/Pods-PrefsMate_Example/Pods-PrefsMate_Example-frameworks.sh", 287 | "${BUILT_PRODUCTS_DIR}/PrefsMate/PrefsMate.framework", 288 | ); 289 | name = "[CP] Embed Pods Frameworks"; 290 | outputPaths = ( 291 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PrefsMate.framework", 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | shellPath = /bin/sh; 295 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PrefsMate_Example/Pods-PrefsMate_Example-frameworks.sh\"\n"; 296 | showEnvVarsInLog = 0; 297 | }; 298 | CBDEB373E12112C39AE06F4D /* [CP] Check Pods Manifest.lock */ = { 299 | isa = PBXShellScriptBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | inputPaths = ( 304 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 305 | "${PODS_ROOT}/Manifest.lock", 306 | ); 307 | name = "[CP] Check Pods Manifest.lock"; 308 | outputPaths = ( 309 | "$(DERIVED_FILE_DIR)/Pods-PrefsMate_Tests-checkManifestLockResult.txt", 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | shellPath = /bin/sh; 313 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 314 | showEnvVarsInLog = 0; 315 | }; 316 | /* End PBXShellScriptBuildPhase section */ 317 | 318 | /* Begin PBXSourcesBuildPhase section */ 319 | 607FACCC1AFB9204008FA782 /* Sources */ = { 320 | isa = PBXSourcesBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | 607FACD81AFB9204008FA782 /* PrefsViewController.swift in Sources */, 324 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | 607FACE11AFB9204008FA782 /* Sources */ = { 329 | isa = PBXSourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 333 | ); 334 | runOnlyForDeploymentPostprocessing = 0; 335 | }; 336 | /* End PBXSourcesBuildPhase section */ 337 | 338 | /* Begin PBXTargetDependency section */ 339 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 340 | isa = PBXTargetDependency; 341 | target = 607FACCF1AFB9204008FA782 /* PrefsMate_Example */; 342 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 343 | }; 344 | /* End PBXTargetDependency section */ 345 | 346 | /* Begin PBXVariantGroup section */ 347 | 6775A06E1F99FB4600D05CC3 /* Prefs.plist */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | 6775A06D1F99FB4600D05CC3 /* Base */, 351 | 6775A06F1F99FB5700D05CC3 /* zh-Hans */, 352 | ); 353 | name = Prefs.plist; 354 | sourceTree = ""; 355 | }; 356 | /* End PBXVariantGroup section */ 357 | 358 | /* Begin XCBuildConfiguration section */ 359 | 607FACED1AFB9204008FA782 /* Debug */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ALWAYS_SEARCH_USER_PATHS = NO; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_CONSTANT_CONVERSION = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_EMPTY_BODY = YES; 371 | CLANG_WARN_ENUM_CONVERSION = YES; 372 | CLANG_WARN_INFINITE_RECURSION = YES; 373 | CLANG_WARN_INT_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 376 | CLANG_WARN_UNREACHABLE_CODE = YES; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 379 | COPY_PHASE_STRIP = NO; 380 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 381 | ENABLE_STRICT_OBJC_MSGSEND = YES; 382 | ENABLE_TESTABILITY = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_DYNAMIC_NO_PIC = NO; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_OPTIMIZATION_LEVEL = 0; 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "DEBUG=1", 389 | "$(inherited)", 390 | ); 391 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 399 | MTL_ENABLE_DEBUG_INFO = YES; 400 | ONLY_ACTIVE_ARCH = YES; 401 | SDKROOT = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 403 | SWIFT_VERSION = 4.0; 404 | }; 405 | name = Debug; 406 | }; 407 | 607FACEE1AFB9204008FA782 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INFINITE_RECURSION = YES; 421 | CLANG_WARN_INT_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_UNREACHABLE_CODE = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 427 | COPY_PHASE_STRIP = NO; 428 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 429 | ENABLE_NS_ASSERTIONS = NO; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | GCC_C_LANGUAGE_STANDARD = gnu99; 432 | GCC_NO_COMMON_BLOCKS = YES; 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 440 | MTL_ENABLE_DEBUG_INFO = NO; 441 | SDKROOT = iphoneos; 442 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 443 | SWIFT_VERSION = 4.0; 444 | VALIDATE_PRODUCT = YES; 445 | }; 446 | name = Release; 447 | }; 448 | 607FACF01AFB9204008FA782 /* Debug */ = { 449 | isa = XCBuildConfiguration; 450 | baseConfigurationReference = 95F35D04A7209424C1FD5BD8 /* Pods-PrefsMate_Example.debug.xcconfig */; 451 | buildSettings = { 452 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 453 | INFOPLIST_FILE = PrefsMate/Info.plist; 454 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 455 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 456 | MODULE_NAME = ExampleApp; 457 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_VERSION = 5.0; 460 | }; 461 | name = Debug; 462 | }; 463 | 607FACF11AFB9204008FA782 /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | baseConfigurationReference = 7599586AF533BC1B8FE7F1AF /* Pods-PrefsMate_Example.release.xcconfig */; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | INFOPLIST_FILE = PrefsMate/Info.plist; 469 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 470 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 471 | MODULE_NAME = ExampleApp; 472 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | SWIFT_VERSION = 5.0; 475 | }; 476 | name = Release; 477 | }; 478 | 607FACF31AFB9204008FA782 /* Debug */ = { 479 | isa = XCBuildConfiguration; 480 | baseConfigurationReference = AA03F62075DC19E7C5209CB4 /* Pods-PrefsMate_Tests.debug.xcconfig */; 481 | buildSettings = { 482 | FRAMEWORK_SEARCH_PATHS = ( 483 | "$(SDKROOT)/Developer/Library/Frameworks", 484 | "$(inherited)", 485 | ); 486 | GCC_PREPROCESSOR_DEFINITIONS = ( 487 | "DEBUG=1", 488 | "$(inherited)", 489 | ); 490 | INFOPLIST_FILE = Tests/Info.plist; 491 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 492 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | SWIFT_VERSION = 4.0; 495 | }; 496 | name = Debug; 497 | }; 498 | 607FACF41AFB9204008FA782 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | baseConfigurationReference = 06CD48987BDBCA6120281244 /* Pods-PrefsMate_Tests.release.xcconfig */; 501 | buildSettings = { 502 | FRAMEWORK_SEARCH_PATHS = ( 503 | "$(SDKROOT)/Developer/Library/Frameworks", 504 | "$(inherited)", 505 | ); 506 | INFOPLIST_FILE = Tests/Info.plist; 507 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 508 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | SWIFT_VERSION = 4.0; 511 | }; 512 | name = Release; 513 | }; 514 | /* End XCBuildConfiguration section */ 515 | 516 | /* Begin XCConfigurationList section */ 517 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "PrefsMate" */ = { 518 | isa = XCConfigurationList; 519 | buildConfigurations = ( 520 | 607FACED1AFB9204008FA782 /* Debug */, 521 | 607FACEE1AFB9204008FA782 /* Release */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "PrefsMate_Example" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | 607FACF01AFB9204008FA782 /* Debug */, 530 | 607FACF11AFB9204008FA782 /* Release */, 531 | ); 532 | defaultConfigurationIsVisible = 0; 533 | defaultConfigurationName = Release; 534 | }; 535 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "PrefsMate_Tests" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | 607FACF31AFB9204008FA782 /* Debug */, 539 | 607FACF41AFB9204008FA782 /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | /* End XCConfigurationList section */ 545 | }; 546 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 547 | } 548 | -------------------------------------------------------------------------------- /Example/PrefsMate.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/PrefsMate.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/PrefsMate.xcodeproj/xcshareddata/xcschemes/PrefsMate-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/PrefsMate.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/PrefsMate.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/PrefsMate/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PrefsMate 4 | // 5 | // Created by 蔡越 on 09/26/2017. 6 | // Copyright © 2017 Nanjing University. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | window = UIWindow(frame: UIScreen.main.bounds) 18 | window?.rootViewController = UINavigationController(rootViewController: PrefsViewController(with: Bundle.main.url(forResource: "Prefs", withExtension: "plist")!)) 19 | window?.makeKeyAndVisible() 20 | return true 21 | } 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Example/PrefsMate/Base.lproj/Prefs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | header 7 | Prefs 8 | prefs 9 | 10 | 11 | detailText 12 | 13 | switchActionName 14 | 15 | switchStatus 16 | 17 | hasDisclosure 18 | 19 | hasSwitch 20 | 21 | title 22 | Change Icon 23 | selectActionName 24 | changeIcon 25 | 26 | 27 | detailText 28 | 29 | switchActionName 30 | handleThemeMode 31 | switchStatus 32 | 33 | hasDisclosure 34 | 35 | hasSwitch 36 | 37 | title 38 | Dark Mode 39 | selectActionName 40 | 41 | 42 | 43 | footer 44 | 45 | 46 | 47 | header 48 | Feedback 49 | prefs 50 | 51 | 52 | detailText 53 | yuecai.nju@gmail.com 54 | switchActionName 55 | 56 | switchStatus 57 | 58 | hasDisclosure 59 | 60 | hasSwitch 61 | 62 | title 63 | Mail 64 | selectActionName 65 | mailAction 66 | 67 | 68 | detailText 69 | @caiyue5 70 | switchActionName 71 | 72 | switchStatus 73 | 74 | hasDisclosure 75 | 76 | hasSwitch 77 | 78 | title 79 | Twitter 80 | selectActionName 81 | twitterAction 82 | 83 | 84 | detailText 85 | @CaiYue_ 86 | switchActionName 87 | 88 | switchStatus 89 | 90 | hasDisclosure 91 | 92 | hasSwitch 93 | 94 | title 95 | Weibo 96 | selectActionName 97 | weiboAction 98 | 99 | 100 | footer 101 | 102 | 103 | 104 | header 105 | Other 106 | prefs 107 | 108 | 109 | detailText 110 | 111 | switchActionName 112 | 113 | switchStatus 114 | 115 | hasDisclosure 116 | 117 | hasSwitch 118 | 119 | title 120 | Rate 121 | selectActionName 122 | rankAction 123 | 124 | 125 | detailText 126 | 127 | switchActionName 128 | 129 | switchStatus 130 | 131 | hasDisclosure 132 | 133 | hasSwitch 134 | 135 | title 136 | Thanks 137 | selectActionName 138 | thankAction 139 | 140 | 141 | footer 142 | PrefsMate 0.3.4 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /Example/PrefsMate/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/PrefsMate/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/PrefsMate/PrefsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PrefsMate 4 | // 5 | // Created by 蔡越 on 09/26/2017. 6 | // Copyright © 2017 Nanjing University. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PrefsMate 11 | 12 | class PrefsViewController: UIViewController { 13 | 14 | private let pListUrl: URL 15 | private lazy var tableView: PrefsTableView = { 16 | return Mate.createPrefsTableView() 17 | }() 18 | 19 | init(with pListUrl: URL) { 20 | self.pListUrl = pListUrl 21 | super.init(nibName: nil, bundle: nil) 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | view.backgroundColor = .white 32 | view.addSubview(tableView) 33 | do { 34 | try Mate.parseWithSource(self, plistUrl: pListUrl) { 35 | tableView.reloadData() 36 | } 37 | } catch { 38 | // Handle with error 39 | } 40 | } 41 | 42 | override func viewWillLayoutSubviews() { 43 | super.viewWillLayoutSubviews() 44 | tableView.frame = view.bounds 45 | } 46 | 47 | } 48 | 49 | extension PrefsViewController: PrefsSupportable { 50 | var switchableItems: [SwitchActionName : SwitchableItemHandler]? { 51 | return [ 52 | "handleThemeMode": { isOn in 53 | print("Dark theme mode is \(isOn)") 54 | } 55 | ] 56 | } 57 | 58 | var selectableItems: [SelectActionName : SelectableItemHandler]? { 59 | return [ 60 | "changeIcon": { 61 | print("Go to icon change view controller") 62 | }, 63 | "mailAction": { 64 | print("Handle with mail action") 65 | }, 66 | "twitterAction": { 67 | print("Handle with twitter action") 68 | }, 69 | "weiboAction": { 70 | print("Handle with weibo action") 71 | }, 72 | "rankAction": { 73 | print("Handle with rank action") 74 | }, 75 | "thankAction": { 76 | print("Handle with thank action") 77 | } 78 | ] 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /Example/PrefsMate/zh-Hans.lproj/Prefs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | header 7 | 偏好设置 8 | prefs 9 | 10 | 11 | detailText 12 | 13 | switchActionName 14 | 15 | switchStatus 16 | 17 | hasDisclosure 18 | 19 | hasSwitch 20 | 21 | title 22 | 更改应用图标 23 | selectActionName 24 | changeIcon 25 | 26 | 27 | detailText 28 | 29 | switchActionName 30 | handleThemeMode 31 | switchStatus 32 | 33 | hasDisclosure 34 | 35 | hasSwitch 36 | 37 | title 38 | 夜间模式 39 | selectActionName 40 | 41 | 42 | 43 | footer 44 | 45 | 46 | 47 | header 48 | 反馈 49 | prefs 50 | 51 | 52 | detailText 53 | yuecai.nju@gmail.com 54 | switchActionName 55 | 56 | switchStatus 57 | 58 | hasDisclosure 59 | 60 | hasSwitch 61 | 62 | title 63 | 邮件 64 | selectActionName 65 | mailAction 66 | 67 | 68 | detailText 69 | @caiyue5 70 | switchActionName 71 | 72 | switchStatus 73 | 74 | hasDisclosure 75 | 76 | hasSwitch 77 | 78 | title 79 | Twitter 80 | selectActionName 81 | twitterAction 82 | 83 | 84 | detailText 85 | @CaiYue_ 86 | switchActionName 87 | 88 | switchStatus 89 | 90 | hasDisclosure 91 | 92 | hasSwitch 93 | 94 | title 95 | 微博 96 | selectActionName 97 | weiboAction 98 | 99 | 100 | footer 101 | 102 | 103 | 104 | header 105 | 其它 106 | prefs 107 | 108 | 109 | detailText 110 | 111 | switchActionName 112 | 113 | switchStatus 114 | 115 | hasDisclosure 116 | 117 | hasSwitch 118 | 119 | title 120 | App Store 评分 121 | selectActionName 122 | rankAction 123 | 124 | 125 | detailText 126 | 127 | switchActionName 128 | 129 | switchStatus 130 | 131 | hasDisclosure 132 | 133 | hasSwitch 134 | 135 | title 136 | 致谢 137 | selectActionName 138 | thankAction 139 | 140 | 141 | footer 142 | PrefsMate 0.3.4 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | import PrefsMate 4 | 5 | class Tests: XCTestCase { 6 | 7 | override func setUp() { 8 | super.setUp() 9 | // Put setup code here. This method is called before the invocation of each test method in the class. 10 | } 11 | 12 | override func tearDown() { 13 | // Put teardown code here. This method is called after the invocation of each test method in the class. 14 | super.tearDown() 15 | } 16 | 17 | func testExample() { 18 | // This is an example of a functional test case. 19 | XCTAssert(true, "Pass") 20 | } 21 | 22 | func testPerformanceExample() { 23 | // This is an example of a performance test case. 24 | self.measure() { 25 | // Put the code you want to measure the time of here. 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 caiyue1993 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "PrefsMate", 6 | platforms: [ 7 | .iOS(.v9) 8 | ], 9 | products: [ 10 | .library(name: "PrefsMate", targets: ["PrefsMate"]) 11 | ], 12 | targets: [ 13 | .target( 14 | name: "PrefsMate", 15 | path: "Source" 16 | ) 17 | ] 18 | ) -------------------------------------------------------------------------------- /PrefsMate.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint PrefsMate.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'PrefsMate' 11 | s.version = '1.0.0' 12 | s.summary = '🐣 Elegant UITableView generator for Swift.' 13 | s.homepage = 'https://github.com/caiyue1993/PrefsMate' 14 | s.license = { :type => 'MIT', :file => 'LICENSE' } 15 | s.author = { 'caiyue1993' => 'yuecai.nju@gmail.com' } 16 | s.source = { :git => 'https://github.com/caiyue1993/PrefsMate.git', :tag => s.version.to_s } 17 | s.social_media_url = 'https://twitter.com/caiyue5' 18 | 19 | s.ios.deployment_target = '9.0' 20 | 21 | s.source_files = 'Source/*.swift' 22 | s.frameworks = 'UIKit' 23 | 24 | end 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PrefsMate 2 | 3 | [![CI Status](http://img.shields.io/travis/caiyue1993/PrefsMate.svg?style=flat)](https://travis-ci.org/caiyue1993/PrefsMate) 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/PrefsMate.svg?style=flat)](http://cocoapods.org/pods/PrefsMate) 6 | [![License](https://img.shields.io/cocoapods/l/PrefsMate.svg?style=flat)](http://cocoapods.org/pods/PrefsMate) 7 | [![Platform](https://img.shields.io/cocoapods/p/PrefsMate.svg?style=flat)](http://cocoapods.org/pods/PrefsMate) 8 | 9 | PrefsMate provide an elegant way to generate UITableView using a property list file(plist file, in short). Also, you can configure actions with its support. Thanks to the **Codable** protocol, it makes the code perfect clean. 10 | 11 | ## Features 12 | - [x] Data Persistence 13 | - [x] Switch Accessory 14 | - [x] Select Action 15 | - [x] Muilty Sections 16 | - [x] Section Header / Footer 17 | - [x] Demo Project 18 | - [x] World Ready 19 | - [ ] More Custom Cells 20 | 21 | ## Background 22 | 23 | In our app, we usually need a UITableView in PrefsViewController(or perhaps named SettingsViewController, whatever). And the interface may just looks like this: 24 | 25 | ![PrefsViewController](https://i.loli.net/2017/10/20/59e9c804d4aa2.png) 26 | 27 | When implementing this kind of stuff, your inner voice must be this: "Writing this UI is fxxking tedious! Is there any help that I can ask for?" 28 | 29 | And congrats! You have come to the right place :). 30 | 31 | ## Usage 32 | 33 | ## 1. Prepare a plist file containing formatted data 34 | 35 | Taking example of the image above, the formatted plist file looks like this: 36 | 37 | ![plist structure](https://i.loli.net/2017/10/20/59e9c921e41aa.png) 38 | 39 | The meaning of each item property is as follows: 40 | 41 | | Property | usage | 42 | | :-----------: | :-----------: | 43 | | `title` | the text on the left | 44 | | `detailText` | the text on the right | 45 | | `hasDisclosure` | whether the cell has a disclosure accessory view| 46 | | `hasSwitch` | whether the cell has a switch | 47 | | `switchStatus` | the status of the switch control | 48 | | `selectActionName` | the name of select action(optional) | 49 | | `switchActionName` | the name of switch action(optional) | 50 | 51 | > Don't be afraid of this long file. In fact you just need to do some clickable things. You could even copy and paste [our plist source code](https://github.com/caiyue1993/PrefsMate/blob/master/Example/PrefsMate/Prefs.plist) first just for your convenience. 52 | 53 | ## 2. Create the table view and do the parsing job 54 | ```swift 55 | let tableView = Mate.createPrefsTableView() 56 | ``` 57 | 58 | You can add the parsing code in viewDidLoad(): 59 | ```swift 60 | do { 61 | try Mate.parseWithSource(self, plistUrl: pListUrl) { 62 | tableView.reloadData() 63 | } 64 | } catch { 65 | // Handle with the error 66 | } 67 | ``` 68 | 69 | ## 3. If needed, let your view controller conform to PrefsSupportable protocol 70 | 71 | If you have select and switch action to handle, PrefsSupportable protocol already considered for you. 72 | 73 | ```swift 74 | public protocol PrefsSupportable { 75 | /// Return a bunch of switchableItems, including their behavior in SwitchableItemHandler. 76 | var switchableItems: [SwitchActionName: SwitchableItemHandler]? { get } 77 | 78 | /// Return a bunch of selectableItems, including their behavior in SelectableItemHandler. 79 | var selectableItems: [SelectActionName: SelectableItemHandler]? { get } 80 | } 81 | ``` 82 | 83 | Taking the switch of night theme for example: 84 | 85 | ```swift 86 | var switchableItems: [SwitchActionName : SwitchableItemHandler]? { 87 | return [ 88 | "handleThemeMode": { isOn in 89 | print("Dark theme mode is \(isOn)") 90 | } 91 | ] 92 | } 93 | var selectableItems: [SelectActionName : SelectableItemHandler]? { 94 | return [ 95 | “changeIcon”: { 96 | print(“Handle change icon action here”) 97 | } 98 | ... 99 | ... 100 | ] 101 | } 102 | ``` 103 | 104 | Then we are done! PrefsMate will do right things for you. 105 | 106 | > Keep in mind: the "handleThemeMode" String must be the same value of `switchActionName` in the plist file. Same on `selectActionName`. 107 | 108 | > In switch actions, PrefsMate already take care of the **data persistence**. So you don’t need to store the user preferences yourself. 109 | 110 | You could refer to [Example project](https://github.com/caiyue1993/PrefsMate/tree/master/Example) for more detail. 111 | 112 | ## Suggestions 113 | 114 | - Being familiar with plist file structure will help you a lot. Sometimes you can directly edit the plist file through "Open As Source Code". 115 | 116 | - If you have an issue, please don't hesitate. Just let me know :) 117 | 118 | ## Example 119 | 120 | To run the example project, clone the repo, and run `pod install` from the Example directory. 121 | 122 | (Cuz this is a new Pod, you may need to `pod update` first.) 123 | 124 | ## Requirements 125 | 126 | - Swift 5 127 | - iOS 9 or later 128 | 129 | ## Installation 130 | 131 | PrefsMate is available through Swift Package Manager & [CocoaPods](http://cocoapods.org). 132 | 133 | ### Swift Package Manager 134 | From Xcode 11, you can use Swift Package Manager to add Kingfisher to your project. 135 | 136 | 1. Select File > Swift Packages > Add Package Dependency. Enter https://github.com/caiyue1993/PrefsMate.git in the "Choose Package Repository" dialog. 137 | 2. In the next page, specify the version resolving rule as "Up to Next Major" with latest release version 138 | 3. After Xcode checking out the source and resolving the version, you can choose the "PrefsMate" library and add it to your app target. 139 | 140 | If you encounter any problem or have a question on adding package to an Xcode project, I suggest the [Adding Package Dependencies to Your App guide](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) article from Apple. 141 | 142 | ### CocoaPods 143 | To install it, simply add the following line to your Podfile: 144 | 145 | ```ruby 146 | pod 'PrefsMate' 147 | ``` 148 | 149 | ## Contact 150 | 151 | - Weibo: [@CaiYue_](http://weibo.com/caiyue233) 152 | - Twitter: [@caiyue5](https://twitter.com/caiyue5) 153 | 154 | ## License 155 | 156 | PrefsMate is available under the MIT license. See the LICENSE file for more info. 157 | -------------------------------------------------------------------------------- /Source/Pref.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pref.swift 3 | // Mate 4 | // 5 | // Created by 蔡越 on 25/09/2017. 6 | // Copyright © 2017 Nanjing University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Pref: Codable { 12 | let title: String 13 | let selectActionName: String 14 | let hasSwitch: Bool 15 | var switchStatus: Bool 16 | let switchActionName: String 17 | let hasDisclosure: Bool 18 | let detailText: String 19 | } 20 | 21 | class SectionOfPrefs: Codable { 22 | var prefs: [Pref] 23 | let header: String 24 | let footer: String 25 | } 26 | -------------------------------------------------------------------------------- /Source/PrefsMate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mate.swift 3 | // PrefsMate 4 | // 5 | // Created by 蔡越 on 26/09/2017. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | import UIKit 26 | 27 | public typealias PrefsTableView = UITableView 28 | 29 | /// PrefsMate provides an elegant way to generate PrefsViewController(or may be SettingsViewController) in your app. All you 30 | /// need is a structured plist file. As for the other things, PrefsMate handles for you. 31 | /// For more reference, navigate to http:// 32 | open class PrefsMate: NSObject { 33 | 34 | /// A nested array to store parsed prefs and to render table view 35 | private(set) var prefs: [SectionOfPrefs] = [] 36 | 37 | /// A url to store plist file location 38 | private var plistUrl: URL! 39 | 40 | /// A NSObject to store the outer object. Cuz it will perform selector outside, we need a property to hold the reference of them 41 | private(set) var source: NSObject! 42 | 43 | 44 | // MARK: - Singleton 45 | 46 | /// Returns a default PrefsMate. A global constant `Mate` is a shortcut of `PrefsMate.default`. 47 | /// 48 | /// - seealso: `Mate` 49 | public static let `default` = PrefsMate() 50 | 51 | 52 | // MARK: - Functions that exposed to outside world 53 | 54 | /// Create a PrefsTableView purely 55 | open func createPrefsTableView() -> PrefsTableView { 56 | let tv = UITableView(frame: .zero, style: .grouped) 57 | tv.delegate = self 58 | tv.dataSource = self 59 | tv.tableFooterView = UIView(frame: .zero) 60 | tv.register(PrefsTableViewCell.self, forCellReuseIdentifier: "cell") 61 | return tv 62 | } 63 | 64 | /// Parse a plist file and ready to do some rendering in completion 65 | /// 66 | /// - parameter source: the seletor that should be performed on. Usually if you are accustomed to write @objc func in the same 67 | /// view controller, pass `self` 68 | /// - parameter plistUrl: the url that the plist file locates 69 | /// - parameter completion: as the name indicates 70 | open func parseWithSource(_ source: NSObject, 71 | plistUrl: URL, 72 | completion: (() -> Void)) throws { 73 | self.source = source 74 | self.plistUrl = plistUrl 75 | 76 | let data = try TransferHelper.default.transferFile(from: plistUrl) 77 | let decoder = PropertyListDecoder() 78 | do { 79 | prefs = try decoder.decode([SectionOfPrefs].self, from: data) 80 | } catch DecodingError.keyNotFound(let key, let context) { 81 | print("Missing key: \(key)") 82 | print("Debug description: \(context.debugDescription)") 83 | } catch DecodingError.valueNotFound(let type, let context) { 84 | print("Missing value for type: \(type)") 85 | print("Debug description: \(context.debugDescription)") 86 | } catch DecodingError.typeMismatch(let type, let context) { 87 | print("Type mismatch for type: \(type)") 88 | print("Debug description: \(context.debugDescription)") 89 | } catch { 90 | print(error.localizedDescription) 91 | } 92 | 93 | completion() 94 | } 95 | 96 | private func writePList() throws { 97 | let encoder = PropertyListEncoder() 98 | do { 99 | let data = try encoder.encode(prefs) 100 | let documentDir = TransferHelper.default.documentDir 101 | try data.write(to: documentDir.appendingPathComponent(self.plistUrl.lastPathComponent)) 102 | } catch EncodingError.invalidValue(let value, let context) { 103 | print("Invalid value: \(value)") 104 | print("Debug description: \(context.debugDescription)") 105 | } catch { 106 | print(error.localizedDescription) 107 | } 108 | } 109 | 110 | } 111 | 112 | extension PrefsMate: UITableViewDelegate { 113 | 114 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 115 | guard let source = source as? PrefsSupportable else { 116 | fatalError("You have to implement PrefsSupportable protocol") 117 | } 118 | 119 | let pref = prefs[indexPath.section].prefs[indexPath.row] 120 | 121 | if let selectableItems = source.selectableItems, let selectAction = selectableItems[pref.selectActionName] { 122 | selectAction() 123 | } else { 124 | print("Go and check it, you may mistype the selectActionName \(pref.selectActionName)") 125 | } 126 | 127 | tableView.deselectRow(at: indexPath, animated: true) 128 | } 129 | 130 | } 131 | 132 | extension PrefsMate: UITableViewDataSource { 133 | 134 | public func numberOfSections(in tableView: UITableView) -> Int { 135 | return prefs.count 136 | } 137 | 138 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 139 | return prefs[section].prefs.count 140 | } 141 | 142 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 143 | guard let source = source as? PrefsSupportable else { 144 | fatalError("You have to implement PrefsSupportable protocol") 145 | } 146 | 147 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PrefsTableViewCell 148 | let pref = prefs[indexPath.section].prefs[indexPath.row] 149 | cell.textLabel?.text = pref.title 150 | cell.detailTextLabel?.text = pref.detailText 151 | cell.accessoryType = pref.hasDisclosure ? .disclosureIndicator : .none 152 | cell.hasSwitch = pref.hasSwitch 153 | cell.switchStatus = pref.switchStatus 154 | cell.switchClosure = { [weak self] isOn in 155 | if let switchableItems = source.switchableItems, let switchAction = switchableItems[pref.switchActionName] { 156 | switchAction(isOn) 157 | } else { 158 | print("You may mismatch the switchActionName \"\(pref.switchActionName)\" in plist file and PrefsSupportable implementation, go and check it" ) 159 | } 160 | pref.switchStatus = isOn 161 | guard let `self` = self else { return } 162 | try? `self`.writePList() 163 | } 164 | 165 | return cell 166 | } 167 | 168 | public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 169 | return prefs[section].header 170 | } 171 | 172 | public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 173 | return prefs[section].footer 174 | } 175 | 176 | } 177 | 178 | // MARK: - Default PrefsMate 179 | public let Mate = PrefsMate.default 180 | -------------------------------------------------------------------------------- /Source/PrefsSupportable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrefsSupportable.swift 3 | // PrefsMate 4 | // 5 | // Created by 蔡越 on 28/09/2017. 6 | // 7 | 8 | import Foundation 9 | 10 | public typealias SwitchableItemHandler = ((Bool) -> Void) 11 | public typealias SelectableItemHandler = (() -> Void) 12 | 13 | public typealias SwitchActionName = String 14 | public typealias SelectActionName = String 15 | 16 | /// A type that give a hand to PrefsMate 17 | /// 18 | /// - seealso: `PrefsMate` 19 | public protocol PrefsSupportable { 20 | 21 | /// Return a bunch of switchableItems, including their behavior in SwitchableItemHandler. 22 | var switchableItems: [SwitchActionName: SwitchableItemHandler]? { get } 23 | 24 | /// Return a bunch of selectableItems, including their behavior in SelectableItemHandler. 25 | var selectableItems: [SelectActionName: SelectableItemHandler]? { get } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Source/PrefsTableViewCell+Switch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewCell+Switch.swift 3 | // Mate 4 | // 5 | // Created by 蔡越 on 25/09/2017. 6 | // Copyright © 2017 Nanjing University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | struct AssociateKeys { 13 | static var switchClosureKey = 0 14 | } 15 | 16 | extension PrefsTableViewCell { 17 | var hasSwitch: Bool { 18 | set { 19 | if newValue { 20 | let `switch` = UISwitch() 21 | `switch`.addTarget(self, action: #selector(switchAction(_:)), for: .valueChanged) 22 | accessoryView = `switch` 23 | } else { 24 | accessoryView = nil 25 | } 26 | } 27 | get { 28 | return false 29 | } 30 | } 31 | 32 | var switchStatus: Bool { 33 | set { 34 | if let av = (accessoryView as? UISwitch) { 35 | av.setOn(newValue, animated: false) 36 | } 37 | } 38 | get { 39 | return false 40 | } 41 | } 42 | 43 | var switchClosure: ((Bool) -> Void)? { 44 | set { 45 | objc_setAssociatedObject(self, &AssociateKeys.switchClosureKey, newValue, .OBJC_ASSOCIATION_RETAIN) 46 | } 47 | get { 48 | return objc_getAssociatedObject(self, &AssociateKeys.switchClosureKey) as! ((Bool) -> Void)? 49 | } 50 | } 51 | 52 | @objc func switchAction(_ sender: UISwitch) { 53 | if let closure = switchClosure { 54 | closure(sender.isOn) 55 | } else { 56 | fatalError("Switch closure undefined.") 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/PrefsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrefsTableViewCell.swift 3 | // PrefsMate 4 | // 5 | // Created by 蔡越 on 28/09/2017. 6 | // 7 | 8 | import UIKit 9 | 10 | class PrefsTableViewCell: UITableViewCell { 11 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 12 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 13 | } 14 | 15 | required init?(coder aDecoder: NSCoder) { 16 | fatalError("init(coder:) has not been implemented") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Source/TransferHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransferHelper.swift 3 | // PrefsMate 4 | // 5 | // Created by 蔡越 on 07/10/2017. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Due to the limitation of the sandbox mechanism in iOS, we can only read the plist 11 | /// file in the bundle. We can not WRITE to it. 12 | /// So we transfer the plist file from the bundle to the Document directory, then we can 13 | /// read and write it for free. 14 | /// And, a specific situation is taken into consideration: when the plist file in the 15 | /// bundle is updated. So we created a "source/" subdirectory in Document directory to 16 | /// record the last one plist file in the bundle. And when bundle file is updated, both 17 | /// the file in Document directory and "source/" subdirectory should be updated too. 18 | 19 | /// For the convenience of explanation, the following test cases are listed: 20 | /// 1. the plist file is created both in the Document directory and "source/" subdirectory 21 | /// for the very first time. 22 | /// 2. when user update the plist file in the Document directory(e.g. change the switch status), the change is persisted in 23 | /// the file. 24 | /// 3. when the plist file in the bundle is updated, it should trigger the update of 25 | /// "source/" and the file in the Document directory. 26 | 27 | class TransferHelper { 28 | 29 | // MARK: - Singleton 30 | 31 | public static let `default` = TransferHelper() 32 | private let fileManager = FileManager.default 33 | 34 | private lazy var sourceDir: URL = { 35 | let destinationDir = documentDir.appendingPathComponent("source/") 36 | return destinationDir 37 | }() 38 | 39 | public lazy var documentDir: URL = { 40 | return try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) 41 | }() 42 | 43 | // MARK: - Functions that related to file transfer 44 | 45 | /// The plist file in the "source/" subdirectory should always keep the same to the current 46 | /// plist file in the bundle. The plist file in the Document directory also need to sync 47 | /// when the plist file in the bundle is updated. 48 | private func storeOriginFile(from originUrl: URL) throws { 49 | if !fileManager.fileExists(atPath: sourceDir.path) { 50 | try fileManager.createDirectory(atPath: sourceDir.path, withIntermediateDirectories: false, attributes: nil) 51 | } 52 | 53 | let destinationUrl = sourceDir.appendingPathComponent(originUrl.lastPathComponent) 54 | if !fileManager.contentsEqual(atPath: destinationUrl.path, andPath: originUrl.path) { 55 | let data = try Data(contentsOf: originUrl) 56 | try data.write(to: destinationUrl) 57 | try data.write(to: documentDir.appendingPathComponent(originUrl.lastPathComponent)) 58 | } 59 | 60 | return 61 | } 62 | 63 | 64 | public func transferFile(from originUrl: URL) throws -> Data { 65 | let destinationUrl = documentDir.appendingPathComponent(originUrl.lastPathComponent) 66 | do { 67 | try TransferHelper.default.storeOriginFile(from: originUrl) 68 | } catch { 69 | print(error.localizedDescription) 70 | } 71 | 72 | if fileManager.fileExists(atPath: destinationUrl.path) { 73 | let data = try Data(contentsOf: destinationUrl) 74 | return data 75 | } 76 | 77 | // If not exists, copy the origin file content and write to destination URL 78 | let data = try Data(contentsOf: originUrl) 79 | try data.write(to: destinationUrl) 80 | return data 81 | } 82 | } 83 | --------------------------------------------------------------------------------