├── .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 | [](https://travis-ci.org/caiyue1993/PrefsMate)
4 |
5 | [](http://cocoapods.org/pods/PrefsMate)
6 | [](http://cocoapods.org/pods/PrefsMate)
7 | [](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 | 
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 | 
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 |
--------------------------------------------------------------------------------