├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .jazzy.yaml
├── .swiftlint.yml
├── Example
├── StateViewControllerExample.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── StateViewControllerExample.xcscheme
└── StateViewControllerExample
│ ├── ActivityIndicatorViewController.swift
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Comment.swift
│ ├── Info.plist
│ ├── ListStateViewController.swift
│ └── TableViewController.swift
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── StateViewController
│ ├── StateViewController.swift
│ └── StateViewControllerTransitioning.swift
├── StateViewController.xcodeproj
├── StateViewController_Info.plist
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── xcshareddata
│ └── xcschemes
│ └── StateViewController-Package.xcscheme
├── docs
├── Classes.html
├── Classes
│ └── StateViewController.html
├── Protocols.html
├── Protocols
│ └── StateViewControllerTransitioning.html
├── badge.svg
├── css
│ ├── highlight.css
│ └── jazzy.css
├── docsets
│ ├── .docset
│ │ └── Contents
│ │ │ ├── Info.plist
│ │ │ └── Resources
│ │ │ ├── Documents
│ │ │ ├── Classes.html
│ │ │ ├── Classes
│ │ │ │ └── StateViewController.html
│ │ │ ├── Protocols.html
│ │ │ ├── Protocols
│ │ │ │ └── StateViewControllerTransitioning.html
│ │ │ ├── css
│ │ │ │ ├── highlight.css
│ │ │ │ └── jazzy.css
│ │ │ ├── img
│ │ │ │ ├── carat.png
│ │ │ │ ├── dash.png
│ │ │ │ ├── gh.png
│ │ │ │ └── spinner.gif
│ │ │ ├── index.html
│ │ │ ├── js
│ │ │ │ ├── jazzy.js
│ │ │ │ ├── jazzy.search.js
│ │ │ │ ├── jquery.min.js
│ │ │ │ ├── lunr.min.js
│ │ │ │ └── typeahead.jquery.js
│ │ │ └── search.json
│ │ │ └── docSet.dsidx
│ └── .tgz
├── img
│ ├── carat.png
│ ├── dash.png
│ ├── gh.png
│ └── spinner.gif
├── index.html
├── js
│ ├── jazzy.js
│ ├── jazzy.search.js
│ ├── jquery.min.js
│ ├── lunr.min.js
│ └── typeahead.jquery.js
├── search.json
└── undocumented.json
└── images
├── between-lifecycle.png
└── during-lifecycle.png
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | - push
4 | - pull_request
5 |
6 | jobs:
7 | iOS:
8 | name: Test iOS
9 | runs-on: macOS-latest
10 | env:
11 | DEVELOPER_DIR: /Applications/Xcode_11.4.1.app/Contents/Developer
12 | strategy:
13 | matrix:
14 | destination: ["OS=13.4.1,name=iPhone 11 Pro"] #, "OS=12.4,name=iPhone XS", "OS=11.4,name=iPhone X", "OS=10.3.1,name=iPhone SE"]
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: iOS - ${{ matrix.destination }}
18 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "StateViewController.xcodeproj" -scheme "StateViewController-Package" -destination "${{ matrix.destination }}" clean build | xcpretty
19 | tvOS:
20 | name: Test tvOS
21 | runs-on: macOS-latest
22 | env:
23 | DEVELOPER_DIR: /Applications/Xcode_11.4.1.app/Contents/Developer
24 | strategy:
25 | matrix:
26 | destination: ["OS=13.4,name=Apple TV 4K"] #, "OS=11.4,name=Apple TV 4K", "OS=10.2,name=Apple TV 1080p"]
27 | steps:
28 | - uses: actions/checkout@v2
29 | - name: tvOS - ${{ matrix.destination }}
30 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "StateViewController.xcodeproj" -scheme "StateViewController-Package" -destination "${{ matrix.destination }}" clean build | xcpretty
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build generated
2 | build/
3 | DerivedData/
4 |
5 | ## Various settings
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 |
16 | ## Other
17 | *.moved-aside
18 | *.xccheckout
19 | *.xcscmblueprint
20 |
21 | ## Obj-C/Swift specific
22 | *.hmap
23 | *.ipa
24 | *.dSYM.zip
25 | *.dSYM
26 |
27 | ## Playgrounds
28 | timeline.xctimeline
29 | playground.xcworkspace
30 |
31 | # Swift Package Manager
32 | Packages/
33 | Package.pins
34 | Package.resolved
35 | .build/
36 |
37 | # Carthage
38 | Carthage/Checkouts
39 | Carthage/Build
--------------------------------------------------------------------------------
/.jazzy.yaml:
--------------------------------------------------------------------------------
1 | output: docs
2 | clean: true
3 | exclude: [Packages, Carthage]
4 | author: David Ask
5 | github_url: https://github.com/davidask/StateViewController
6 | github_file_prefix: https://github.com/davidask/StateViewController/tree/master
7 | readme: README.md
8 | theme: fullwidth
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | excluded:
2 | - Carthage
3 |
4 | file_length:
5 | ignore_comment_only_lines: true
6 |
7 | identifier_name:
8 | min_length: 2
9 | validates_start_with_lowercase: false
10 | allowed_symbols:
11 | - "_"
12 |
13 | line_length:
14 | ignores_comments: true
15 |
16 | type_name:
17 | min_length: 2
18 |
19 | disabled_rules:
20 | - identifier_name
21 |
22 | nesting:
23 | type_level:
24 | warning: 3
25 | statement_level:
26 | warning: 10
27 |
28 | analyzer_rules:
29 | - unused_declaration
30 | - unused_import
31 | opt_in_rules:
32 | - anyobject_protocol
33 | - array_init
34 | - attributes
35 | - closure_end_indentation
36 | - closure_spacing
37 | - collection_alignment
38 | - contains_over_filter_count
39 | - contains_over_filter_is_empty
40 | - contains_over_first_not_nil
41 | - discouraged_object_literal
42 | - empty_count
43 | - empty_string
44 | - empty_xctest_method
45 | - explicit_init
46 | - extension_access_modifier
47 | - fallthrough
48 | - fatal_error_message
49 | - file_name
50 | - first_where
51 | - flatmap_over_map_reduce
52 | - identical_operands
53 | - joined_default_parameter
54 | - legacy_random
55 | - let_var_whitespace
56 | - last_where
57 | - literal_expression_end_indentation
58 | - lower_acl_than_parent
59 | - modifier_order
60 | - nimble_operator
61 | - nslocalizedstring_key
62 | - number_separator
63 | - object_literal
64 | - operator_usage_whitespace
65 | - overridden_super_call
66 | - override_in_extension
67 | - pattern_matching_keywords
68 | - private_action
69 | - private_outlet
70 | - prohibited_super_call
71 | - quick_discouraged_call
72 | - quick_discouraged_focused_test
73 | - quick_discouraged_pending_test
74 | - reduce_into
75 | - redundant_nil_coalescing
76 | - redundant_type_annotation
77 | - single_test_class
78 | - sorted_first_last
79 | - sorted_imports
80 | - static_operator
81 | - strong_iboutlet
82 | - toggle_bool
83 | - unavailable_function
84 | - unneeded_parentheses_in_closure_argument
85 | - unowned_variable_capture
86 | - untyped_error_in_catch
87 | - vertical_parameter_alignment_on_call
88 | - vertical_whitespace_closing_braces
89 | - xct_specific_matcher
90 | - yoda_condition
--------------------------------------------------------------------------------
/Example/StateViewControllerExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B76FCF8D24DBECFE0014697B /* StateViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B76FCF8B24DBECFA0014697B /* StateViewController.framework */; };
11 | B76FCF8E24DBECFE0014697B /* StateViewController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B76FCF8B24DBECFA0014697B /* StateViewController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
12 | B77C0EEC2125ED3700E88C7E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C0EEB2125ED3700E88C7E /* AppDelegate.swift */; };
13 | B77C0EEE2125ED3700E88C7E /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C0EED2125ED3700E88C7E /* TableViewController.swift */; };
14 | B77C0EF12125ED3800E88C7E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B77C0EEF2125ED3800E88C7E /* Main.storyboard */; };
15 | B77C0EF32125ED3900E88C7E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B77C0EF22125ED3900E88C7E /* Assets.xcassets */; };
16 | B77C0EF62125ED3900E88C7E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B77C0EF42125ED3900E88C7E /* LaunchScreen.storyboard */; };
17 | B77C0F0C2125EF4000E88C7E /* ListStateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C0F0B2125EF4000E88C7E /* ListStateViewController.swift */; };
18 | B77C0F0E2125EFC300E88C7E /* ActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C0F0D2125EFC300E88C7E /* ActivityIndicatorViewController.swift */; };
19 | B77C0F102125F14400E88C7E /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C0F0F2125F14400E88C7E /* Comment.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXContainerItemProxy section */
23 | B76FCF8A24DBECFA0014697B /* PBXContainerItemProxy */ = {
24 | isa = PBXContainerItemProxy;
25 | containerPortal = B77C0EFD2125ED5800E88C7E /* StateViewController.xcodeproj */;
26 | proxyType = 2;
27 | remoteGlobalIDString = "StateViewController::StateViewController::Product";
28 | remoteInfo = StateViewController;
29 | };
30 | /* End PBXContainerItemProxy section */
31 |
32 | /* Begin PBXCopyFilesBuildPhase section */
33 | B76FCF8F24DBECFE0014697B /* Embed Frameworks */ = {
34 | isa = PBXCopyFilesBuildPhase;
35 | buildActionMask = 2147483647;
36 | dstPath = "";
37 | dstSubfolderSpec = 10;
38 | files = (
39 | B76FCF8E24DBECFE0014697B /* StateViewController.framework in Embed Frameworks */,
40 | );
41 | name = "Embed Frameworks";
42 | runOnlyForDeploymentPostprocessing = 0;
43 | };
44 | /* End PBXCopyFilesBuildPhase section */
45 |
46 | /* Begin PBXFileReference section */
47 | B77C0EE82125ED3700E88C7E /* StateViewControllerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StateViewControllerExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
48 | B77C0EEB2125ED3700E88C7E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
49 | B77C0EED2125ED3700E88C7E /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; };
50 | B77C0EF02125ED3800E88C7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
51 | B77C0EF22125ED3900E88C7E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
52 | B77C0EF52125ED3900E88C7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
53 | B77C0EF72125ED3900E88C7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
54 | B77C0EFD2125ED5800E88C7E /* StateViewController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = StateViewController.xcodeproj; path = ../StateViewController.xcodeproj; sourceTree = ""; };
55 | B77C0F0B2125EF4000E88C7E /* ListStateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStateViewController.swift; sourceTree = ""; };
56 | B77C0F0D2125EFC300E88C7E /* ActivityIndicatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorViewController.swift; sourceTree = ""; };
57 | B77C0F0F2125F14400E88C7E /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; };
58 | /* End PBXFileReference section */
59 |
60 | /* Begin PBXFrameworksBuildPhase section */
61 | B77C0EE52125ED3700E88C7E /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | B76FCF8D24DBECFE0014697B /* StateViewController.framework in Frameworks */,
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | /* End PBXFrameworksBuildPhase section */
70 |
71 | /* Begin PBXGroup section */
72 | B76FCF8C24DBECFE0014697B /* Frameworks */ = {
73 | isa = PBXGroup;
74 | children = (
75 | );
76 | name = Frameworks;
77 | sourceTree = "";
78 | };
79 | B77C0EDF2125ED3700E88C7E = {
80 | isa = PBXGroup;
81 | children = (
82 | B77C0EFD2125ED5800E88C7E /* StateViewController.xcodeproj */,
83 | B77C0EEA2125ED3700E88C7E /* StateViewControllerExample */,
84 | B77C0EE92125ED3700E88C7E /* Products */,
85 | B76FCF8C24DBECFE0014697B /* Frameworks */,
86 | );
87 | sourceTree = "";
88 | };
89 | B77C0EE92125ED3700E88C7E /* Products */ = {
90 | isa = PBXGroup;
91 | children = (
92 | B77C0EE82125ED3700E88C7E /* StateViewControllerExample.app */,
93 | );
94 | name = Products;
95 | sourceTree = "";
96 | };
97 | B77C0EEA2125ED3700E88C7E /* StateViewControllerExample */ = {
98 | isa = PBXGroup;
99 | children = (
100 | B77C0EEB2125ED3700E88C7E /* AppDelegate.swift */,
101 | B77C0EED2125ED3700E88C7E /* TableViewController.swift */,
102 | B77C0F0B2125EF4000E88C7E /* ListStateViewController.swift */,
103 | B77C0F0D2125EFC300E88C7E /* ActivityIndicatorViewController.swift */,
104 | B77C0EEF2125ED3800E88C7E /* Main.storyboard */,
105 | B77C0EF22125ED3900E88C7E /* Assets.xcassets */,
106 | B77C0EF42125ED3900E88C7E /* LaunchScreen.storyboard */,
107 | B77C0EF72125ED3900E88C7E /* Info.plist */,
108 | B77C0F0F2125F14400E88C7E /* Comment.swift */,
109 | );
110 | path = StateViewControllerExample;
111 | sourceTree = "";
112 | };
113 | B77C0EFE2125ED5800E88C7E /* Products */ = {
114 | isa = PBXGroup;
115 | children = (
116 | B76FCF8B24DBECFA0014697B /* StateViewController.framework */,
117 | );
118 | name = Products;
119 | sourceTree = "";
120 | };
121 | /* End PBXGroup section */
122 |
123 | /* Begin PBXNativeTarget section */
124 | B77C0EE72125ED3700E88C7E /* StateViewControllerExample */ = {
125 | isa = PBXNativeTarget;
126 | buildConfigurationList = B77C0EFA2125ED3900E88C7E /* Build configuration list for PBXNativeTarget "StateViewControllerExample" */;
127 | buildPhases = (
128 | B77C0EE42125ED3700E88C7E /* Sources */,
129 | B77C0EE52125ED3700E88C7E /* Frameworks */,
130 | B77C0EE62125ED3700E88C7E /* Resources */,
131 | B76FCF8F24DBECFE0014697B /* Embed Frameworks */,
132 | );
133 | buildRules = (
134 | );
135 | dependencies = (
136 | );
137 | name = StateViewControllerExample;
138 | productName = StateViewControllerExample;
139 | productReference = B77C0EE82125ED3700E88C7E /* StateViewControllerExample.app */;
140 | productType = "com.apple.product-type.application";
141 | };
142 | /* End PBXNativeTarget section */
143 |
144 | /* Begin PBXProject section */
145 | B77C0EE02125ED3700E88C7E /* Project object */ = {
146 | isa = PBXProject;
147 | attributes = {
148 | LastSwiftUpdateCheck = 0940;
149 | LastUpgradeCheck = 0940;
150 | ORGANIZATIONNAME = Formbound;
151 | TargetAttributes = {
152 | B77C0EE72125ED3700E88C7E = {
153 | CreatedOnToolsVersion = 9.4.1;
154 | LastSwiftMigration = 1150;
155 | };
156 | };
157 | };
158 | buildConfigurationList = B77C0EE32125ED3700E88C7E /* Build configuration list for PBXProject "StateViewControllerExample" */;
159 | compatibilityVersion = "Xcode 9.3";
160 | developmentRegion = en;
161 | hasScannedForEncodings = 0;
162 | knownRegions = (
163 | en,
164 | Base,
165 | );
166 | mainGroup = B77C0EDF2125ED3700E88C7E;
167 | productRefGroup = B77C0EE92125ED3700E88C7E /* Products */;
168 | projectDirPath = "";
169 | projectReferences = (
170 | {
171 | ProductGroup = B77C0EFE2125ED5800E88C7E /* Products */;
172 | ProjectRef = B77C0EFD2125ED5800E88C7E /* StateViewController.xcodeproj */;
173 | },
174 | );
175 | projectRoot = "";
176 | targets = (
177 | B77C0EE72125ED3700E88C7E /* StateViewControllerExample */,
178 | );
179 | };
180 | /* End PBXProject section */
181 |
182 | /* Begin PBXReferenceProxy section */
183 | B76FCF8B24DBECFA0014697B /* StateViewController.framework */ = {
184 | isa = PBXReferenceProxy;
185 | fileType = wrapper.framework;
186 | path = StateViewController.framework;
187 | remoteRef = B76FCF8A24DBECFA0014697B /* PBXContainerItemProxy */;
188 | sourceTree = BUILT_PRODUCTS_DIR;
189 | };
190 | /* End PBXReferenceProxy section */
191 |
192 | /* Begin PBXResourcesBuildPhase section */
193 | B77C0EE62125ED3700E88C7E /* Resources */ = {
194 | isa = PBXResourcesBuildPhase;
195 | buildActionMask = 2147483647;
196 | files = (
197 | B77C0EF62125ED3900E88C7E /* LaunchScreen.storyboard in Resources */,
198 | B77C0EF32125ED3900E88C7E /* Assets.xcassets in Resources */,
199 | B77C0EF12125ED3800E88C7E /* Main.storyboard in Resources */,
200 | );
201 | runOnlyForDeploymentPostprocessing = 0;
202 | };
203 | /* End PBXResourcesBuildPhase section */
204 |
205 | /* Begin PBXSourcesBuildPhase section */
206 | B77C0EE42125ED3700E88C7E /* Sources */ = {
207 | isa = PBXSourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | B77C0F102125F14400E88C7E /* Comment.swift in Sources */,
211 | B77C0EEE2125ED3700E88C7E /* TableViewController.swift in Sources */,
212 | B77C0F0E2125EFC300E88C7E /* ActivityIndicatorViewController.swift in Sources */,
213 | B77C0EEC2125ED3700E88C7E /* AppDelegate.swift in Sources */,
214 | B77C0F0C2125EF4000E88C7E /* ListStateViewController.swift in Sources */,
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | /* End PBXSourcesBuildPhase section */
219 |
220 | /* Begin PBXVariantGroup section */
221 | B77C0EEF2125ED3800E88C7E /* Main.storyboard */ = {
222 | isa = PBXVariantGroup;
223 | children = (
224 | B77C0EF02125ED3800E88C7E /* Base */,
225 | );
226 | name = Main.storyboard;
227 | sourceTree = "";
228 | };
229 | B77C0EF42125ED3900E88C7E /* LaunchScreen.storyboard */ = {
230 | isa = PBXVariantGroup;
231 | children = (
232 | B77C0EF52125ED3900E88C7E /* Base */,
233 | );
234 | name = LaunchScreen.storyboard;
235 | sourceTree = "";
236 | };
237 | /* End PBXVariantGroup section */
238 |
239 | /* Begin XCBuildConfiguration section */
240 | B77C0EF82125ED3900E88C7E /* Debug */ = {
241 | isa = XCBuildConfiguration;
242 | buildSettings = {
243 | ALWAYS_SEARCH_USER_PATHS = NO;
244 | CLANG_ANALYZER_NONNULL = YES;
245 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
247 | CLANG_CXX_LIBRARY = "libc++";
248 | CLANG_ENABLE_MODULES = YES;
249 | CLANG_ENABLE_OBJC_ARC = YES;
250 | CLANG_ENABLE_OBJC_WEAK = YES;
251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
252 | CLANG_WARN_BOOL_CONVERSION = YES;
253 | CLANG_WARN_COMMA = YES;
254 | CLANG_WARN_CONSTANT_CONVERSION = YES;
255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
258 | CLANG_WARN_EMPTY_BODY = YES;
259 | CLANG_WARN_ENUM_CONVERSION = YES;
260 | CLANG_WARN_INFINITE_RECURSION = YES;
261 | CLANG_WARN_INT_CONVERSION = YES;
262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
267 | CLANG_WARN_STRICT_PROTOTYPES = YES;
268 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
270 | CLANG_WARN_UNREACHABLE_CODE = YES;
271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
272 | CODE_SIGN_IDENTITY = "iPhone Developer";
273 | COPY_PHASE_STRIP = NO;
274 | DEBUG_INFORMATION_FORMAT = dwarf;
275 | ENABLE_STRICT_OBJC_MSGSEND = YES;
276 | ENABLE_TESTABILITY = YES;
277 | GCC_C_LANGUAGE_STANDARD = gnu11;
278 | GCC_DYNAMIC_NO_PIC = NO;
279 | GCC_NO_COMMON_BLOCKS = YES;
280 | GCC_OPTIMIZATION_LEVEL = 0;
281 | GCC_PREPROCESSOR_DEFINITIONS = (
282 | "DEBUG=1",
283 | "$(inherited)",
284 | );
285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
287 | GCC_WARN_UNDECLARED_SELECTOR = YES;
288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
289 | GCC_WARN_UNUSED_FUNCTION = YES;
290 | GCC_WARN_UNUSED_VARIABLE = YES;
291 | IPHONEOS_DEPLOYMENT_TARGET = 11.4;
292 | MTL_ENABLE_DEBUG_INFO = YES;
293 | ONLY_ACTIVE_ARCH = YES;
294 | SDKROOT = iphoneos;
295 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
296 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
297 | };
298 | name = Debug;
299 | };
300 | B77C0EF92125ED3900E88C7E /* Release */ = {
301 | isa = XCBuildConfiguration;
302 | buildSettings = {
303 | ALWAYS_SEARCH_USER_PATHS = NO;
304 | CLANG_ANALYZER_NONNULL = YES;
305 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
307 | CLANG_CXX_LIBRARY = "libc++";
308 | CLANG_ENABLE_MODULES = YES;
309 | CLANG_ENABLE_OBJC_ARC = YES;
310 | CLANG_ENABLE_OBJC_WEAK = YES;
311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
312 | CLANG_WARN_BOOL_CONVERSION = YES;
313 | CLANG_WARN_COMMA = YES;
314 | CLANG_WARN_CONSTANT_CONVERSION = YES;
315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
317 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
318 | CLANG_WARN_EMPTY_BODY = YES;
319 | CLANG_WARN_ENUM_CONVERSION = YES;
320 | CLANG_WARN_INFINITE_RECURSION = YES;
321 | CLANG_WARN_INT_CONVERSION = YES;
322 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
323 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
324 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
326 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
327 | CLANG_WARN_STRICT_PROTOTYPES = YES;
328 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
329 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
330 | CLANG_WARN_UNREACHABLE_CODE = YES;
331 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
332 | CODE_SIGN_IDENTITY = "iPhone Developer";
333 | COPY_PHASE_STRIP = NO;
334 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
335 | ENABLE_NS_ASSERTIONS = NO;
336 | ENABLE_STRICT_OBJC_MSGSEND = YES;
337 | GCC_C_LANGUAGE_STANDARD = gnu11;
338 | GCC_NO_COMMON_BLOCKS = YES;
339 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
340 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
341 | GCC_WARN_UNDECLARED_SELECTOR = YES;
342 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
343 | GCC_WARN_UNUSED_FUNCTION = YES;
344 | GCC_WARN_UNUSED_VARIABLE = YES;
345 | IPHONEOS_DEPLOYMENT_TARGET = 11.4;
346 | MTL_ENABLE_DEBUG_INFO = NO;
347 | SDKROOT = iphoneos;
348 | SWIFT_COMPILATION_MODE = wholemodule;
349 | SWIFT_OPTIMIZATION_LEVEL = "-O";
350 | VALIDATE_PRODUCT = YES;
351 | };
352 | name = Release;
353 | };
354 | B77C0EFB2125ED3900E88C7E /* Debug */ = {
355 | isa = XCBuildConfiguration;
356 | buildSettings = {
357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
358 | CODE_SIGN_STYLE = Automatic;
359 | DEVELOPMENT_TEAM = "";
360 | INFOPLIST_FILE = StateViewControllerExample/Info.plist;
361 | LD_RUNPATH_SEARCH_PATHS = (
362 | "$(inherited)",
363 | "@executable_path/Frameworks",
364 | );
365 | PRODUCT_BUNDLE_IDENTIFIER = com.formbound.StateViewControllerExample;
366 | PRODUCT_NAME = "$(TARGET_NAME)";
367 | SWIFT_VERSION = 5.0;
368 | TARGETED_DEVICE_FAMILY = "1,2";
369 | };
370 | name = Debug;
371 | };
372 | B77C0EFC2125ED3900E88C7E /* Release */ = {
373 | isa = XCBuildConfiguration;
374 | buildSettings = {
375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
376 | CODE_SIGN_STYLE = Automatic;
377 | DEVELOPMENT_TEAM = "";
378 | INFOPLIST_FILE = StateViewControllerExample/Info.plist;
379 | LD_RUNPATH_SEARCH_PATHS = (
380 | "$(inherited)",
381 | "@executable_path/Frameworks",
382 | );
383 | PRODUCT_BUNDLE_IDENTIFIER = com.formbound.StateViewControllerExample;
384 | PRODUCT_NAME = "$(TARGET_NAME)";
385 | SWIFT_VERSION = 5.0;
386 | TARGETED_DEVICE_FAMILY = "1,2";
387 | };
388 | name = Release;
389 | };
390 | /* End XCBuildConfiguration section */
391 |
392 | /* Begin XCConfigurationList section */
393 | B77C0EE32125ED3700E88C7E /* Build configuration list for PBXProject "StateViewControllerExample" */ = {
394 | isa = XCConfigurationList;
395 | buildConfigurations = (
396 | B77C0EF82125ED3900E88C7E /* Debug */,
397 | B77C0EF92125ED3900E88C7E /* Release */,
398 | );
399 | defaultConfigurationIsVisible = 0;
400 | defaultConfigurationName = Release;
401 | };
402 | B77C0EFA2125ED3900E88C7E /* Build configuration list for PBXNativeTarget "StateViewControllerExample" */ = {
403 | isa = XCConfigurationList;
404 | buildConfigurations = (
405 | B77C0EFB2125ED3900E88C7E /* Debug */,
406 | B77C0EFC2125ED3900E88C7E /* Release */,
407 | );
408 | defaultConfigurationIsVisible = 0;
409 | defaultConfigurationName = Release;
410 | };
411 | /* End XCConfigurationList section */
412 | };
413 | rootObject = B77C0EE02125ED3700E88C7E /* Project object */;
414 | }
415 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample.xcodeproj/xcshareddata/xcschemes/StateViewControllerExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/ActivityIndicatorViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityIndicatorViewController.swift
3 | // StateViewControllerExample
4 | //
5 | // Created by David Ask on 2018-08-16.
6 | // Copyright © 2018 Formbound. All rights reserved.
7 | //
8 |
9 | import StateViewController
10 | import UIKit
11 |
12 | class ActivityIndicatorViewController: UIViewController {
13 |
14 | @IBOutlet private var activityIndicator: UIActivityIndicatorView!
15 |
16 | @IBOutlet private var activityIndicatorContainer: UIView!
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | activityIndicatorContainer.layer.cornerRadius = 5
21 | }
22 |
23 | override func viewWillAppear(_ animated: Bool) {
24 | super.viewWillAppear(animated)
25 |
26 | activityIndicator.startAnimating()
27 | }
28 |
29 | override func viewDidDisappear(_ animated: Bool) {
30 | super.viewDidDisappear(animated)
31 |
32 | activityIndicator.stopAnimating()
33 | }
34 | }
35 |
36 | extension ActivityIndicatorViewController: StateViewControllerTransitioning {
37 |
38 | func stateTransitionDuration(isAppearing: Bool) -> TimeInterval {
39 | return 0.5
40 | }
41 |
42 | func stateTransitionWillBegin(isAppearing: Bool) {
43 | if isAppearing {
44 | view.alpha = 0
45 | activityIndicatorContainer.transform = CGAffineTransform
46 | .identity
47 | .scaledBy(x: 0.5, y: 0.5)
48 | }
49 | }
50 |
51 | func stateTransitionDidEnd(isAppearing: Bool) {
52 | view.alpha = 1
53 | activityIndicatorContainer.transform = .identity
54 | }
55 |
56 | func animateAlongsideStateTransition(isAppearing: Bool) {
57 | if isAppearing {
58 | view.alpha = 1
59 | activityIndicatorContainer.transform = .identity
60 | } else {
61 | view.alpha = 0
62 | activityIndicatorContainer.transform = CGAffineTransform
63 | .identity
64 | .scaledBy(x: 1.5, y: 1.5)
65 | }
66 | }
67 |
68 | func stateTransitionDelay(isAppearing: Bool) -> TimeInterval {
69 | return isAppearing ? 0 : 0.5
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // StateViewControllerExample
4 | //
5 | // Created by David Ask on 2018-08-16.
6 | // Copyright © 2018 Formbound. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 | }
15 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/Comment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Comment.swift
3 | // StateViewControllerExample
4 | //
5 | // Created by David Ask on 2018-08-16.
6 | // Copyright © 2018 Formbound. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Comment: Decodable {
12 | let id: Int
13 | let name: String
14 | let email: String
15 | let body: String
16 |
17 | static func loadComments(_ completion: @escaping (Error?, [Comment]?) -> Void) {
18 |
19 | let url = URL(string: "https://jsonplaceholder.typicode.com/comments")!
20 |
21 | let task = URLSession.shared.dataTask(with: url) { data, _, error in
22 | if let error = error {
23 | completion(error, nil)
24 | return
25 | }
26 |
27 | guard let data = data else {
28 | completion(nil, [])
29 | return
30 | }
31 |
32 | do {
33 | completion(nil, try JSONDecoder().decode([Comment].self, from: data))
34 | } catch {
35 | completion(error, nil)
36 | }
37 | }
38 |
39 | task.resume()
40 | }
41 | }
42 |
43 | extension Comment: Equatable {
44 | static func == (lhs: Comment, rhs: Comment) -> Bool {
45 | return lhs.id == rhs.id
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/ListStateViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListStateViewController.swift
3 | // StateViewControllerExample
4 | //
5 | // Created by David Ask on 2018-08-16.
6 | // Copyright © 2018 Formbound. All rights reserved.
7 | //
8 |
9 | import StateViewController
10 | import UIKit
11 |
12 | enum ListStateViewControllerState: Equatable {
13 | case loading
14 | case ready([Comment])
15 | }
16 |
17 | class ListStateViewController: StateViewController {
18 |
19 | lazy var reloadBarButtonItem = UIBarButtonItem(
20 | barButtonSystemItem: .refresh,
21 | target: self,
22 | action: #selector(refresh)
23 | )
24 |
25 | override func awakeFromNib() {
26 | super.awakeFromNib()
27 |
28 | title = "Comments"
29 | }
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 | }
34 |
35 | private lazy var tableViewController: TableViewController = {
36 | let storyboard = UIStoryboard(name: "Main", bundle: .main)
37 | // swiftlint:disable force_cast
38 | return storyboard.instantiateViewController(withIdentifier: "tableViewController") as! TableViewController
39 | // swiftlint:enable force_cast
40 | }()
41 |
42 | override func loadAppearanceState() -> ListStateViewControllerState {
43 | return .loading
44 | }
45 |
46 | override func children(for state: ListStateViewControllerState) -> [UIViewController] {
47 | switch state {
48 | case .loading:
49 |
50 | let storyboard = UIStoryboard(name: "Main", bundle: .main)
51 |
52 | return [
53 | tableViewController,
54 | storyboard.instantiateViewController(withIdentifier: "activityIndicatorController")
55 | ]
56 | case .ready:
57 | return [
58 | tableViewController
59 | ]
60 | }
61 | }
62 |
63 | override func willTransition(to nextState: ListStateViewControllerState, animated: Bool) {
64 | switch nextState {
65 | case .loading:
66 | navigationItem.setRightBarButton(nil, animated: animated)
67 | case .ready(let comments):
68 | navigationItem.setRightBarButton(reloadBarButtonItem, animated: animated)
69 | tableViewController.comments = comments
70 | }
71 | }
72 |
73 | override func didTransition(from previousState: ListStateViewControllerState?, animated: Bool) {
74 | switch currentState {
75 | case .loading:
76 | Comment.loadComments { _, comments in
77 | guard let comments = comments else {
78 | return
79 | }
80 |
81 | DispatchQueue.main.async {
82 | self.setNeedsStateTransition(to: .ready(comments), animated: true)
83 | }
84 | }
85 | case .ready:
86 | break
87 | }
88 | }
89 |
90 | @objc
91 | func refresh() {
92 | self.setNeedsStateTransition(to: .loading, animated: true)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Example/StateViewControllerExample/TableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewController.swift
3 | // StateViewControllerExample
4 | //
5 | // Created by David Ask on 2018-08-16.
6 | // Copyright © 2018 Formbound. All rights reserved.
7 | //
8 |
9 | import StateViewController
10 | import UIKit
11 |
12 | class TableViewController: UITableViewController {
13 |
14 | var comments: [Comment] = [] {
15 | didSet {
16 | tableView.reloadData()
17 | }
18 | }
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | }
23 |
24 | override func numberOfSections(in tableView: UITableView) -> Int {
25 | return 1
26 | }
27 |
28 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
29 | return comments.count
30 | }
31 |
32 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
33 | return tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
34 | }
35 |
36 | override func tableView(
37 | _ tableView: UITableView,
38 | willDisplay cell: UITableViewCell,
39 | forRowAt indexPath: IndexPath
40 | ) {
41 |
42 | let comment = comments[indexPath.row]
43 |
44 | cell.textLabel?.text = comment.name
45 | cell.detailTextLabel?.text = comment.body
46 | }
47 | }
48 | //
49 | //
50 | //extension TableViewController: StateViewControllerTransitioning {
51 | //
52 | // func stateTransitionDuration(isAppearing: Bool) -> TimeInterval {
53 | // return 0.25
54 | // }
55 | //
56 | // func stateTransitionWillBegin(isAppearing: Bool) {
57 | // if isAppearing {
58 | // view.alpha = 0
59 | // view.transform = CGAffineTransform.identity.scaledBy(x: 0.95, y: 0.95)
60 | // }
61 | // }
62 | //
63 | // func stateTransitionDidEnd(isAppearing: Bool) {
64 | // view.alpha = 1
65 | // view.transform = .identity
66 | // }
67 | //
68 | // func animateAlongsideStateTransition(isAppearing: Bool) {
69 | // if isAppearing {
70 | // view.alpha = 1
71 | // view.transform = .identity
72 | // } else {
73 | // view.alpha = 0
74 | // view.transform = CGAffineTransform.identity.scaledBy(x: 0.95, y: 0.95)
75 | // }
76 | // }
77 | //
78 | // func stateTransitionDelay(isAppearing: Bool) -> TimeInterval {
79 | // return 0
80 | // }
81 | //}
82 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 David Ask
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "StateViewController",
7 | platforms: [
8 | .iOS(.v8),
9 | .tvOS(.v9)
10 | ],
11 | products: [
12 | .library(
13 | name: "StateViewController",
14 | targets: ["StateViewController"])
15 | ],
16 | targets: [
17 | .target(
18 | name: "StateViewController",
19 | dependencies: [])
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # StateViewController
3 |
4 | When creating rich view controllers, a single view controller class is often tasked with managing the appearance of many other views, controls, and other user interface elements based on a state. That state, in turn, is often derived from multiple sources that need to be synchronized to correctly represent a single reliable state. Usually the end result is known as the *Massive View Controller* problem, often solved by attempts to abandon the [MVC](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html) pattern, the primary design pattern in UIKit. While other patterns, such as [MVVM](https://en.wikipedia.org/wiki/Model–view–viewmodel) or [MVP](https://en.wikipedia.org/wiki/Model–view–presenter), can solve some issues, going with the grain rather than against makes interacting with UIKit more accommodating.
5 |
6 | This repository houses a `UIViewController` subclass, enabling modularization and decoupling of view controllers, reducing the size of individual view controllers substantially, without the need for abandoning MVC as a design pattern.
7 |
8 |
9 |
10 | ## Requirements
11 |
12 | * iOS 8.0+
13 | * tvOS 9.0+
14 |
15 | ## Overview
16 | `StateViewController` is a container view controller that presents one or more view controllers for any given state that you define, such as `loading`, `list`, or `editing`. It manages the appearance cycles of each child view controller, making sure that the view life cycle of the child view controllers are intact and in order, notifying you about state transitions and which child view controllers are about to appear or disappear from the view hierarchy. This allows you to compose multiple view controllers and re-use them throughout the app. The state view controller also provides extensive support for animating the transition between states.
17 |
18 | Which view controller(s) are visible on screen is dictated by `children(for:)`.
19 |
20 | ### State transitions during appearance transition
21 | When presented on screen, the a state view controller requires an initial state as a starting point. During its appearance transition, the `loadAppearanceState()` method is invoked to query the state appropriate to transition to as the state view controller appears on screen.
22 | If the appearance transition is animated, the state transition animation is respected, and target child view controllers have the option to appear asynchronously. If the appearance transition is not animated, all child view controllers are immediately placed on screen.
23 |
24 | `loadAppearanceState()` must execute synchronously, and is a good place to query any persistence layer for available data, determining whether a final state is ready.
25 |
26 |
27 | 
28 |
29 | ### State transitions while on screen
30 |
31 | When on-screen, invoking `setNeedsTransition:to:` will trigger a transition from the current state to the target state. A common practice is to have the transition from one state to another to trigger an an asynchronous operation (such as a network call), which upon completion, requests a third state based on the success of the asynchronous operation.
32 |
33 | 
34 |
35 | ## Documentation
36 |
37 | The source code documentation can be found [here](https://davidask.github.io/StateViewController/).
38 |
39 | ## Installation
40 | This module is available via [Carthage](https://github.com/Carthage/Carthage). Modify your [Cartfile](https://github.com/Carthage/Carthage#quick-start) to include `StateViewController`:
41 |
42 | ```
43 | github "davidask/StateViewController"
44 | ```
45 |
46 | ## Usage
47 |
48 | ```swift
49 | import StateViewController
50 | ```
51 |
52 | ### Subclassing StateViewController
53 |
54 | To use `StateViewController` you must override it. The class specifies a generic with a subtype of `State`. The state type can be designed to house the actual model data required by your view controller, but that's an optional design decision. For instance, you can create a state that simply determines an abstract state:
55 |
56 | ```swift
57 | enum MyState {
58 | case loading
59 | case ready
60 | case error
61 | }
62 | ```
63 |
64 | Or, you can define a state which in itself contains model data:
65 | ```swift
66 | enum MyState {
67 | case loading
68 | case ready(MyModel)
69 | case error(Error)
70 | }
71 | ```
72 |
73 | Once you have a state, create a subclass of `StateViewController`.
74 |
75 | ```swift
76 | class MyStateViewController: StateViewController
77 | ```
78 |
79 | Each time `StateViewController` is about to appear on screen it will call its `loadAppearanceState()` method. This method returns a state which should be ready for display as soon as the view controller is on screen. Override this method to determine what state is appropriate to display immediately, depending on cached data or the contents of your database.
80 |
81 | ```swift
82 | override func loadAppearanceState() -> MyState {
83 | if let myModel = MyCache.cachedModel {
84 | return .ready(myModel)
85 | } else {
86 | return .loading
87 | }
88 | }
89 | ```
90 |
91 | Each state can be represented by zero or more view controllers. To provide which view controllers are visible for what state, override `children(for:)`.
92 |
93 | ```swift
94 | override func children(for state: MyState) -> [UIViewController] {
95 | switch state {
96 | case .loading:
97 | return [ActivityIndicatorViewController()]
98 | case .ready:
99 | return [myTableViewController]
100 | case .error:
101 | return [ErrorViewController()]
102 | }
103 | }
104 | ```
105 |
106 | You receive callbacks for when a state transition will begin, and when it has finished.
107 | `willTransition(to:animated:)` is a good place to prepare your child view controllers for appearance.
108 |
109 | ```swift
110 | override func willTransition(to nextState: MyState, animated: Bool) {
111 | switch nextState {
112 | case .ready(let model):
113 | navigationItem.setRightBarButton(myBarButtonItem, animated: animated)
114 | myTableViewController.setRows(model.objects)
115 | default:
116 | navigationItem.setRightBarButton(nil, animated: animated)
117 | }
118 | }
119 | ```
120 |
121 | When `didTransition(from:animated:)` is called, a state transition has finished successfully. This is a good time to invoke other methods which in turn will trigger another state transition.
122 |
123 | ```swift
124 | override func didTransition(from previousState: State?, animated: Bool) {
125 | switch currentState {
126 | case .loading:
127 | fetchData { model in
128 | self.setNeedsTransition(to: .ready(model), animated: true)
129 | }
130 | defualt:
131 | break
132 | }
133 | }
134 | ```
135 |
136 | Your `StateViewController` is now ready, and will switch between view controllers depending on state. Using `setNeedsTransition(:to:animated:)` you can transition between various states during the life cycle of your state view controller subclass.
137 |
138 | Multiple other callbacks are available for determining when a child view controller is appearing or disappearing. Please reference the documentation or the [Example](/Example).
139 |
140 |
141 | ### Providing transitions between child view controllers
142 |
143 | Child view controllers of `StateViewController` conforming to the [`StateViewControllerTransitioning`](Sources/StateViewController/StateViewControllerTransitioning.swift) protocol can individually control their own transition. The available methods provide functionality for:
144 |
145 | - Specifying duration and delayed start of an animated transition
146 | - Preparing the view controller for presentation
147 | - Performing animations along side an animated transition
148 | - Performing operations after a transition
149 |
150 |
151 | ## Example
152 |
153 | In the example application included in this project the state view controller switches between two view controllers. Firstly, it displays and animates the transition of an activity indicator view controller while a network call is being performed. Once the network call is successfully completed it transitions into a state displaying a table view with the loaded content.
154 |
155 | ## Contribute
156 |
157 | Please feel welcome contributing to **StateViewController**, check the ``LICENSE`` file for more info.
158 |
159 | ## Credits
160 |
161 | David Ask
162 |
--------------------------------------------------------------------------------
/Sources/StateViewController/StateViewControllerTransitioning.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 |
3 | import UIKit
4 |
5 | /// View controllers can conform to this protocol to provide their desired
6 | /// state transitioning behaviour when contained in a `StateViewController`.
7 | public protocol StateViewControllerTransitioning: AnyObject {
8 |
9 | /// Returns the animation duration for a state transition of this view controller.
10 | ///
11 | /// - Parameter isAppearing: Whether this view controller is appearing.
12 | /// - Returns: A transition duration.
13 | func stateTransitionDuration(isAppearing: Bool) -> TimeInterval
14 |
15 | /// Notifies that a state transition will begin for this view controller.
16 | ///
17 | /// - Parameter isAppearing: Whether this view controller is appearing.
18 | func stateTransitionWillBegin(isAppearing: Bool)
19 |
20 | /// Notifies that a state transition did end for this view controller.
21 | ///
22 | /// - Parameter isAppearing: Whether this view controller is appearing.
23 | func stateTransitionDidEnd(isAppearing: Bool)
24 |
25 | /// Animations performed alongside the state transition of this view controller.
26 | ///
27 | /// - Parameter isAppearing: Whether this view controller is appearing.
28 | func animateAlongsideStateTransition(isAppearing: Bool)
29 |
30 | /// Returns the animation delay for a state transition of the provided view controller.
31 | ///
32 | /// - Parameter isAppearing: Whether this view controller is appearing.
33 | /// - Returns: A transition duration.
34 | func stateTransitionDelay(isAppearing: Bool) -> TimeInterval
35 | }
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------
/StateViewController.xcodeproj/StateViewController_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/StateViewController.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = "1";
4 | objectVersion = "46";
5 | objects = {
6 | "OBJ_1" = {
7 | isa = "PBXProject";
8 | attributes = {
9 | LastSwiftMigration = "9999";
10 | LastUpgradeCheck = "9999";
11 | };
12 | buildConfigurationList = "OBJ_2";
13 | compatibilityVersion = "Xcode 3.2";
14 | developmentRegion = "en";
15 | hasScannedForEncodings = "0";
16 | knownRegions = (
17 | "en"
18 | );
19 | mainGroup = "OBJ_5";
20 | productRefGroup = "OBJ_12";
21 | projectDirPath = ".";
22 | targets = (
23 | "StateViewController::StateViewController",
24 | "StateViewController::SwiftPMPackageDescription"
25 | );
26 | };
27 | "OBJ_10" = {
28 | isa = "PBXFileReference";
29 | path = "StateViewControllerTransitioning.swift";
30 | sourceTree = "";
31 | };
32 | "OBJ_11" = {
33 | isa = "PBXGroup";
34 | children = (
35 | );
36 | name = "Tests";
37 | path = "";
38 | sourceTree = "SOURCE_ROOT";
39 | };
40 | "OBJ_12" = {
41 | isa = "PBXGroup";
42 | children = (
43 | "StateViewController::StateViewController::Product"
44 | );
45 | name = "Products";
46 | path = "";
47 | sourceTree = "BUILT_PRODUCTS_DIR";
48 | };
49 | "OBJ_14" = {
50 | isa = "PBXFileReference";
51 | path = "images";
52 | sourceTree = "SOURCE_ROOT";
53 | };
54 | "OBJ_15" = {
55 | isa = "PBXFileReference";
56 | path = "StateViewControllerExample";
57 | sourceTree = "SOURCE_ROOT";
58 | };
59 | "OBJ_16" = {
60 | isa = "PBXFileReference";
61 | path = "Example";
62 | sourceTree = "SOURCE_ROOT";
63 | };
64 | "OBJ_17" = {
65 | isa = "PBXFileReference";
66 | path = "Carthage";
67 | sourceTree = "SOURCE_ROOT";
68 | };
69 | "OBJ_18" = {
70 | isa = "PBXFileReference";
71 | path = "docs";
72 | sourceTree = "SOURCE_ROOT";
73 | };
74 | "OBJ_19" = {
75 | isa = "PBXFileReference";
76 | path = "build";
77 | sourceTree = "SOURCE_ROOT";
78 | };
79 | "OBJ_2" = {
80 | isa = "XCConfigurationList";
81 | buildConfigurations = (
82 | "OBJ_3",
83 | "OBJ_4"
84 | );
85 | defaultConfigurationIsVisible = "0";
86 | defaultConfigurationName = "Release";
87 | };
88 | "OBJ_20" = {
89 | isa = "PBXFileReference";
90 | path = "LICENSE";
91 | sourceTree = "";
92 | };
93 | "OBJ_21" = {
94 | isa = "PBXFileReference";
95 | path = "README.md";
96 | sourceTree = "";
97 | };
98 | "OBJ_23" = {
99 | isa = "XCConfigurationList";
100 | buildConfigurations = (
101 | "OBJ_24",
102 | "OBJ_25"
103 | );
104 | defaultConfigurationIsVisible = "0";
105 | defaultConfigurationName = "Release";
106 | };
107 | "OBJ_24" = {
108 | isa = "XCBuildConfiguration";
109 | buildSettings = {
110 | ENABLE_TESTABILITY = "YES";
111 | FRAMEWORK_SEARCH_PATHS = (
112 | "$(inherited)",
113 | "$(PLATFORM_DIR)/Developer/Library/Frameworks"
114 | );
115 | HEADER_SEARCH_PATHS = (
116 | "$(inherited)"
117 | );
118 | INFOPLIST_FILE = "StateViewController.xcodeproj/StateViewController_Info.plist";
119 | IPHONEOS_DEPLOYMENT_TARGET = "8.0";
120 | LD_RUNPATH_SEARCH_PATHS = (
121 | "$(inherited)",
122 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"
123 | );
124 | MACOSX_DEPLOYMENT_TARGET = "10.10";
125 | OTHER_CFLAGS = (
126 | "$(inherited)"
127 | );
128 | OTHER_LDFLAGS = (
129 | "$(inherited)"
130 | );
131 | OTHER_SWIFT_FLAGS = (
132 | "$(inherited)"
133 | );
134 | PRODUCT_BUNDLE_IDENTIFIER = "StateViewController";
135 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
136 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
137 | SKIP_INSTALL = "YES";
138 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
139 | "$(inherited)"
140 | );
141 | SWIFT_VERSION = "5.0";
142 | TARGET_NAME = "StateViewController";
143 | TVOS_DEPLOYMENT_TARGET = "9.0";
144 | WATCHOS_DEPLOYMENT_TARGET = "2.0";
145 | };
146 | name = "Debug";
147 | };
148 | "OBJ_25" = {
149 | isa = "XCBuildConfiguration";
150 | buildSettings = {
151 | ENABLE_TESTABILITY = "YES";
152 | FRAMEWORK_SEARCH_PATHS = (
153 | "$(inherited)",
154 | "$(PLATFORM_DIR)/Developer/Library/Frameworks"
155 | );
156 | HEADER_SEARCH_PATHS = (
157 | "$(inherited)"
158 | );
159 | INFOPLIST_FILE = "StateViewController.xcodeproj/StateViewController_Info.plist";
160 | IPHONEOS_DEPLOYMENT_TARGET = "8.0";
161 | LD_RUNPATH_SEARCH_PATHS = (
162 | "$(inherited)",
163 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"
164 | );
165 | MACOSX_DEPLOYMENT_TARGET = "10.10";
166 | OTHER_CFLAGS = (
167 | "$(inherited)"
168 | );
169 | OTHER_LDFLAGS = (
170 | "$(inherited)"
171 | );
172 | OTHER_SWIFT_FLAGS = (
173 | "$(inherited)"
174 | );
175 | PRODUCT_BUNDLE_IDENTIFIER = "StateViewController";
176 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
177 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
178 | SKIP_INSTALL = "YES";
179 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
180 | "$(inherited)"
181 | );
182 | SWIFT_VERSION = "5.0";
183 | TARGET_NAME = "StateViewController";
184 | TVOS_DEPLOYMENT_TARGET = "9.0";
185 | WATCHOS_DEPLOYMENT_TARGET = "2.0";
186 | };
187 | name = "Release";
188 | };
189 | "OBJ_26" = {
190 | isa = "PBXSourcesBuildPhase";
191 | files = (
192 | "OBJ_27",
193 | "OBJ_28"
194 | );
195 | };
196 | "OBJ_27" = {
197 | isa = "PBXBuildFile";
198 | fileRef = "OBJ_9";
199 | };
200 | "OBJ_28" = {
201 | isa = "PBXBuildFile";
202 | fileRef = "OBJ_10";
203 | };
204 | "OBJ_29" = {
205 | isa = "PBXFrameworksBuildPhase";
206 | files = (
207 | );
208 | };
209 | "OBJ_3" = {
210 | isa = "XCBuildConfiguration";
211 | buildSettings = {
212 | CLANG_ENABLE_OBJC_ARC = "YES";
213 | COMBINE_HIDPI_IMAGES = "YES";
214 | COPY_PHASE_STRIP = "NO";
215 | DEBUG_INFORMATION_FORMAT = "dwarf";
216 | DYLIB_INSTALL_NAME_BASE = "@rpath";
217 | ENABLE_NS_ASSERTIONS = "YES";
218 | GCC_OPTIMIZATION_LEVEL = "0";
219 | GCC_PREPROCESSOR_DEFINITIONS = (
220 | "$(inherited)",
221 | "SWIFT_PACKAGE=1",
222 | "DEBUG=1"
223 | );
224 | MACOSX_DEPLOYMENT_TARGET = "10.10";
225 | ONLY_ACTIVE_ARCH = "YES";
226 | OTHER_SWIFT_FLAGS = (
227 | "$(inherited)",
228 | "-DXcode"
229 | );
230 | PRODUCT_NAME = "$(TARGET_NAME)";
231 | SDKROOT = "macosx";
232 | SUPPORTED_PLATFORMS = (
233 | "macosx",
234 | "iphoneos",
235 | "iphonesimulator",
236 | "appletvos",
237 | "appletvsimulator",
238 | "watchos",
239 | "watchsimulator"
240 | );
241 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
242 | "$(inherited)",
243 | "SWIFT_PACKAGE",
244 | "DEBUG"
245 | );
246 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
247 | USE_HEADERMAP = "NO";
248 | };
249 | name = "Debug";
250 | };
251 | "OBJ_31" = {
252 | isa = "XCConfigurationList";
253 | buildConfigurations = (
254 | "OBJ_32",
255 | "OBJ_33"
256 | );
257 | defaultConfigurationIsVisible = "0";
258 | defaultConfigurationName = "Release";
259 | };
260 | "OBJ_32" = {
261 | isa = "XCBuildConfiguration";
262 | buildSettings = {
263 | LD = "/usr/bin/true";
264 | OTHER_SWIFT_FLAGS = (
265 | "-swift-version",
266 | "5",
267 | "-I",
268 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2",
269 | "-target",
270 | "x86_64-apple-macosx10.10",
271 | "-sdk",
272 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk",
273 | "-package-description-version",
274 | "5.2.0"
275 | );
276 | SWIFT_VERSION = "5.0";
277 | };
278 | name = "Debug";
279 | };
280 | "OBJ_33" = {
281 | isa = "XCBuildConfiguration";
282 | buildSettings = {
283 | LD = "/usr/bin/true";
284 | OTHER_SWIFT_FLAGS = (
285 | "-swift-version",
286 | "5",
287 | "-I",
288 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2",
289 | "-target",
290 | "x86_64-apple-macosx10.10",
291 | "-sdk",
292 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk",
293 | "-package-description-version",
294 | "5.2.0"
295 | );
296 | SWIFT_VERSION = "5.0";
297 | };
298 | name = "Release";
299 | };
300 | "OBJ_34" = {
301 | isa = "PBXSourcesBuildPhase";
302 | files = (
303 | "OBJ_35"
304 | );
305 | };
306 | "OBJ_35" = {
307 | isa = "PBXBuildFile";
308 | fileRef = "OBJ_6";
309 | };
310 | "OBJ_4" = {
311 | isa = "XCBuildConfiguration";
312 | buildSettings = {
313 | CLANG_ENABLE_OBJC_ARC = "YES";
314 | COMBINE_HIDPI_IMAGES = "YES";
315 | COPY_PHASE_STRIP = "YES";
316 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
317 | DYLIB_INSTALL_NAME_BASE = "@rpath";
318 | GCC_OPTIMIZATION_LEVEL = "s";
319 | GCC_PREPROCESSOR_DEFINITIONS = (
320 | "$(inherited)",
321 | "SWIFT_PACKAGE=1"
322 | );
323 | MACOSX_DEPLOYMENT_TARGET = "10.10";
324 | OTHER_SWIFT_FLAGS = (
325 | "$(inherited)",
326 | "-DXcode"
327 | );
328 | PRODUCT_NAME = "$(TARGET_NAME)";
329 | SDKROOT = "macosx";
330 | SUPPORTED_PLATFORMS = (
331 | "macosx",
332 | "iphoneos",
333 | "iphonesimulator",
334 | "appletvos",
335 | "appletvsimulator",
336 | "watchos",
337 | "watchsimulator"
338 | );
339 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
340 | "$(inherited)",
341 | "SWIFT_PACKAGE"
342 | );
343 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
344 | USE_HEADERMAP = "NO";
345 | };
346 | name = "Release";
347 | };
348 | "OBJ_5" = {
349 | isa = "PBXGroup";
350 | children = (
351 | "OBJ_6",
352 | "OBJ_7",
353 | "OBJ_11",
354 | "OBJ_12",
355 | "OBJ_14",
356 | "OBJ_15",
357 | "OBJ_16",
358 | "OBJ_17",
359 | "OBJ_18",
360 | "OBJ_19",
361 | "OBJ_20",
362 | "OBJ_21"
363 | );
364 | path = "";
365 | sourceTree = "";
366 | };
367 | "OBJ_6" = {
368 | isa = "PBXFileReference";
369 | explicitFileType = "sourcecode.swift";
370 | path = "Package.swift";
371 | sourceTree = "";
372 | };
373 | "OBJ_7" = {
374 | isa = "PBXGroup";
375 | children = (
376 | "OBJ_8"
377 | );
378 | name = "Sources";
379 | path = "";
380 | sourceTree = "SOURCE_ROOT";
381 | };
382 | "OBJ_8" = {
383 | isa = "PBXGroup";
384 | children = (
385 | "OBJ_9",
386 | "OBJ_10"
387 | );
388 | name = "StateViewController";
389 | path = "Sources/StateViewController";
390 | sourceTree = "SOURCE_ROOT";
391 | };
392 | "OBJ_9" = {
393 | isa = "PBXFileReference";
394 | path = "StateViewController.swift";
395 | sourceTree = "";
396 | };
397 | "StateViewController::StateViewController" = {
398 | isa = "PBXNativeTarget";
399 | buildConfigurationList = "OBJ_23";
400 | buildPhases = (
401 | "OBJ_26",
402 | "OBJ_29"
403 | );
404 | dependencies = (
405 | );
406 | name = "StateViewController";
407 | productName = "StateViewController";
408 | productReference = "StateViewController::StateViewController::Product";
409 | productType = "com.apple.product-type.framework";
410 | };
411 | "StateViewController::StateViewController::Product" = {
412 | isa = "PBXFileReference";
413 | path = "StateViewController.framework";
414 | sourceTree = "BUILT_PRODUCTS_DIR";
415 | };
416 | "StateViewController::SwiftPMPackageDescription" = {
417 | isa = "PBXNativeTarget";
418 | buildConfigurationList = "OBJ_31";
419 | buildPhases = (
420 | "OBJ_34"
421 | );
422 | dependencies = (
423 | );
424 | name = "StateViewControllerPackageDescription";
425 | productName = "StateViewControllerPackageDescription";
426 | productType = "com.apple.product-type.framework";
427 | };
428 | };
429 | rootObject = "OBJ_1";
430 | }
431 |
--------------------------------------------------------------------------------
/StateViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/StateViewController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/StateViewController.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
--------------------------------------------------------------------------------
/StateViewController.xcodeproj/xcshareddata/xcschemes/StateViewController-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
53 |
54 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/docs/Classes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Classes Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
34 |
35 |
41 |
42 |
43 |
44 |
45 | Reference
46 |
47 | Classes Reference
48 |
49 |
50 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
Classes
76 |
The following classes are available globally.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
A container view controller that manages the appearance of one or more child view controller for any given state.
99 |
Overview
100 |
101 |
This class is designed to make stateful view controller programming easier. Typically in iOS development,
102 | views representing multiple states are managed in one single view controller, leading to large view controller
103 | classes that quickly become hard to work with and overlook at a glance. For instance, a view controller may
104 | display an activity indicator while a network call is performed, leaving the view controller to have to directly
105 | manipulate view hierarhy for each state. Furthermore, the state of a view controller tends to be represented by
106 | conditions that are hard to synchronize, easily becoming a source of bugs and unexpected behavior.
107 | With StateViewController
each state can be represented by one or more view controllers.
108 | This allows you to composite view controllers in self-contained classes resulting in smaller view
109 | controllers and better ability modularize your view controller code, with clear separation between states.
110 |
Subclassing notes
111 |
112 |
You must subclass StateViewController
and define a state for the view controller you are creating.
113 |
enum MyViewControllerState {
114 | case loading
115 | case ready
116 | }
117 |
118 |
119 |
Note: Your state must conform to Equatable
in order for StateViewController
to distinguish between states.
120 |
121 |
Override loadAppearanceState()
to determine which state is being represented each time this view controller
122 | is appearing on screen. In this method is appropriate to query your model layer to determine whether data needed
123 | for a certain state is available or not.
124 |
override func loadAppearanceState () -> MyViewControllerState {
125 | if model . isDataAvailable {
126 | return . ready
127 | } else {
128 | return . loading
129 | }
130 | }
131 |
132 |
133 |
To determine which content view controllers represent a particular state, you must override
134 | children(for:)
.
135 |
override func children ( for state : MyViewControllerState ) -> [ UIViewController ] {
136 | switch state {
137 | case . loading :
138 | return [ ActivityIndicatorViewController ()]
139 | case . empty :
140 | return [ myChild ]
141 | }
142 | }
143 |
144 |
145 |
Callback methods are overridable, notifying you when a state transition is being performed, and what child
146 | view controllers are being presented as a result of a state transition.
147 |
148 |
Using willTransition(to:animated:)
you should prepare view controller representing the state being transition to
149 | with the appropriate data.
150 |
override func willTransition ( to state : MyViewControllerState , animated : Bool ) {
151 | switch state {
152 | case . ready :
153 | myChild . content = myLoadedContent
154 | case . loading :
155 | break
156 | }
157 | }
158 |
159 |
160 |
Overriding didTransition(to:animated:)
is an appropriate place to invoke methods that eventually results in
161 | a state transition being requested using setNeedsTransition(to:animated:)
, as it ensures that any previous state
162 | transitions has been fully completed.
163 |
override func didTransition ( from previousState : MyViewControllerState ?, animated : Bool ) {
164 | switch state {
165 | case . ready :
166 | break
167 | case . loading :
168 | model . loadData { result in
169 | self . myLoadedContent = result
170 | self . setNeedsTransition ( to : . ready , animated : true )
171 | }
172 | }
173 | }
174 |
175 |
176 |
You may also override loadChildContainerView()
to provide a custom container view for your
177 | content view controllers, allowing you to manipulate the view hierarchy above and below the content view
178 | controller container view.
179 |
Animating state transitions
180 |
181 |
By default, no animations are performed between states. To enable animations, you have three options:
182 |
183 |
184 | Set defaultStateTransitioningCoordinator
185 | Override stateTransitionCoordinator(for:)
in your StateViewController
subclasses
186 | Conform view controllers contained in StateViewController
to StateViewControllerTransitioning
.
187 |
188 |
189 |
See more
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
205 |
206 |
207 |
208 |
--------------------------------------------------------------------------------
/docs/Protocols.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Protocols Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
43 |
44 |
45 | Reference
46 |
47 | Protocols Reference
48 |
49 |
50 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
Protocols
76 |
The following protocols are available globally.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
View controllers can conform to this protocol to provide their desired
99 | state transitioning behaviour when contained in a StateViewController
.
100 |
101 |
See more
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/docs/Protocols/StateViewControllerTransitioning.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | StateViewControllerTransitioning Protocol Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
43 |
44 |
45 | Reference
46 |
47 | StateViewControllerTransitioning Protocol Reference
48 |
49 |
50 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
StateViewControllerTransitioning
76 |
View controllers can conform to this protocol to provide their desired
77 | state transitioning behaviour when contained in a StateViewController
.
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
94 |
95 |
96 |
97 |
98 |
99 |
Returns the animation duration for a state transition of this view controller.
100 |
101 |
102 |
103 |
104 |
105 |
106 |
113 |
114 |
115 |
116 |
117 |
118 |
Notifies that a state transition will begin for this view controller.
119 |
120 |
121 |
122 |
123 |
124 |
125 |
132 |
133 |
134 |
135 |
136 |
137 |
Notifies that a state transition did end for this view controller.
138 |
139 |
140 |
141 |
142 |
143 |
144 |
151 |
152 |
153 |
154 |
155 |
156 |
Animations performed alongside the state transition of this view controller.
157 |
158 |
159 |
160 |
161 |
162 |
163 |
170 |
171 |
172 |
173 |
174 |
175 |
Returns the animation delay for a state transition of the provided view controller.
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/docs/badge.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | documentation
17 |
18 |
19 | documentation
20 |
21 |
22 | 0%
23 |
24 |
25 | 0%
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/docs/css/highlight.css:
--------------------------------------------------------------------------------
1 | /* Credit to https://gist.github.com/wataru420/2048287 */
2 | .highlight {
3 | /* Comment */
4 | /* Error */
5 | /* Keyword */
6 | /* Operator */
7 | /* Comment.Multiline */
8 | /* Comment.Preproc */
9 | /* Comment.Single */
10 | /* Comment.Special */
11 | /* Generic.Deleted */
12 | /* Generic.Deleted.Specific */
13 | /* Generic.Emph */
14 | /* Generic.Error */
15 | /* Generic.Heading */
16 | /* Generic.Inserted */
17 | /* Generic.Inserted.Specific */
18 | /* Generic.Output */
19 | /* Generic.Prompt */
20 | /* Generic.Strong */
21 | /* Generic.Subheading */
22 | /* Generic.Traceback */
23 | /* Keyword.Constant */
24 | /* Keyword.Declaration */
25 | /* Keyword.Pseudo */
26 | /* Keyword.Reserved */
27 | /* Keyword.Type */
28 | /* Literal.Number */
29 | /* Literal.String */
30 | /* Name.Attribute */
31 | /* Name.Builtin */
32 | /* Name.Class */
33 | /* Name.Constant */
34 | /* Name.Entity */
35 | /* Name.Exception */
36 | /* Name.Function */
37 | /* Name.Namespace */
38 | /* Name.Tag */
39 | /* Name.Variable */
40 | /* Operator.Word */
41 | /* Text.Whitespace */
42 | /* Literal.Number.Float */
43 | /* Literal.Number.Hex */
44 | /* Literal.Number.Integer */
45 | /* Literal.Number.Oct */
46 | /* Literal.String.Backtick */
47 | /* Literal.String.Char */
48 | /* Literal.String.Doc */
49 | /* Literal.String.Double */
50 | /* Literal.String.Escape */
51 | /* Literal.String.Heredoc */
52 | /* Literal.String.Interpol */
53 | /* Literal.String.Other */
54 | /* Literal.String.Regex */
55 | /* Literal.String.Single */
56 | /* Literal.String.Symbol */
57 | /* Name.Builtin.Pseudo */
58 | /* Name.Variable.Class */
59 | /* Name.Variable.Global */
60 | /* Name.Variable.Instance */
61 | /* Literal.Number.Integer.Long */ }
62 | .highlight .c {
63 | color: #999988;
64 | font-style: italic; }
65 | .highlight .err {
66 | color: #a61717;
67 | background-color: #e3d2d2; }
68 | .highlight .k {
69 | color: #000000;
70 | font-weight: bold; }
71 | .highlight .o {
72 | color: #000000;
73 | font-weight: bold; }
74 | .highlight .cm {
75 | color: #999988;
76 | font-style: italic; }
77 | .highlight .cp {
78 | color: #999999;
79 | font-weight: bold; }
80 | .highlight .c1 {
81 | color: #999988;
82 | font-style: italic; }
83 | .highlight .cs {
84 | color: #999999;
85 | font-weight: bold;
86 | font-style: italic; }
87 | .highlight .gd {
88 | color: #000000;
89 | background-color: #ffdddd; }
90 | .highlight .gd .x {
91 | color: #000000;
92 | background-color: #ffaaaa; }
93 | .highlight .ge {
94 | color: #000000;
95 | font-style: italic; }
96 | .highlight .gr {
97 | color: #aa0000; }
98 | .highlight .gh {
99 | color: #999999; }
100 | .highlight .gi {
101 | color: #000000;
102 | background-color: #ddffdd; }
103 | .highlight .gi .x {
104 | color: #000000;
105 | background-color: #aaffaa; }
106 | .highlight .go {
107 | color: #888888; }
108 | .highlight .gp {
109 | color: #555555; }
110 | .highlight .gs {
111 | font-weight: bold; }
112 | .highlight .gu {
113 | color: #aaaaaa; }
114 | .highlight .gt {
115 | color: #aa0000; }
116 | .highlight .kc {
117 | color: #000000;
118 | font-weight: bold; }
119 | .highlight .kd {
120 | color: #000000;
121 | font-weight: bold; }
122 | .highlight .kp {
123 | color: #000000;
124 | font-weight: bold; }
125 | .highlight .kr {
126 | color: #000000;
127 | font-weight: bold; }
128 | .highlight .kt {
129 | color: #445588; }
130 | .highlight .m {
131 | color: #009999; }
132 | .highlight .s {
133 | color: #d14; }
134 | .highlight .na {
135 | color: #008080; }
136 | .highlight .nb {
137 | color: #0086B3; }
138 | .highlight .nc {
139 | color: #445588;
140 | font-weight: bold; }
141 | .highlight .no {
142 | color: #008080; }
143 | .highlight .ni {
144 | color: #800080; }
145 | .highlight .ne {
146 | color: #990000;
147 | font-weight: bold; }
148 | .highlight .nf {
149 | color: #990000; }
150 | .highlight .nn {
151 | color: #555555; }
152 | .highlight .nt {
153 | color: #000080; }
154 | .highlight .nv {
155 | color: #008080; }
156 | .highlight .ow {
157 | color: #000000;
158 | font-weight: bold; }
159 | .highlight .w {
160 | color: #bbbbbb; }
161 | .highlight .mf {
162 | color: #009999; }
163 | .highlight .mh {
164 | color: #009999; }
165 | .highlight .mi {
166 | color: #009999; }
167 | .highlight .mo {
168 | color: #009999; }
169 | .highlight .sb {
170 | color: #d14; }
171 | .highlight .sc {
172 | color: #d14; }
173 | .highlight .sd {
174 | color: #d14; }
175 | .highlight .s2 {
176 | color: #d14; }
177 | .highlight .se {
178 | color: #d14; }
179 | .highlight .sh {
180 | color: #d14; }
181 | .highlight .si {
182 | color: #d14; }
183 | .highlight .sx {
184 | color: #d14; }
185 | .highlight .sr {
186 | color: #009926; }
187 | .highlight .s1 {
188 | color: #d14; }
189 | .highlight .ss {
190 | color: #990073; }
191 | .highlight .bp {
192 | color: #999999; }
193 | .highlight .vc {
194 | color: #008080; }
195 | .highlight .vg {
196 | color: #008080; }
197 | .highlight .vi {
198 | color: #008080; }
199 | .highlight .il {
200 | color: #009999; }
201 |
--------------------------------------------------------------------------------
/docs/css/jazzy.css:
--------------------------------------------------------------------------------
1 | *, *:before, *:after {
2 | box-sizing: inherit; }
3 |
4 | body {
5 | margin: 0;
6 | background: #fff;
7 | color: #333;
8 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | letter-spacing: .2px;
10 | -webkit-font-smoothing: antialiased;
11 | box-sizing: border-box; }
12 |
13 | h1 {
14 | font-size: 2rem;
15 | font-weight: 700;
16 | margin: 1.275em 0 0.6em; }
17 |
18 | h2 {
19 | font-size: 1.75rem;
20 | font-weight: 700;
21 | margin: 1.275em 0 0.3em; }
22 |
23 | h3 {
24 | font-size: 1.5rem;
25 | font-weight: 700;
26 | margin: 1em 0 0.3em; }
27 |
28 | h4 {
29 | font-size: 1.25rem;
30 | font-weight: 700;
31 | margin: 1.275em 0 0.85em; }
32 |
33 | h5 {
34 | font-size: 1rem;
35 | font-weight: 700;
36 | margin: 1.275em 0 0.85em; }
37 |
38 | h6 {
39 | font-size: 1rem;
40 | font-weight: 700;
41 | margin: 1.275em 0 0.85em;
42 | color: #777; }
43 |
44 | p {
45 | margin: 0 0 1em; }
46 |
47 | ul, ol {
48 | padding: 0 0 0 2em;
49 | margin: 0 0 0.85em; }
50 |
51 | blockquote {
52 | margin: 0 0 0.85em;
53 | padding: 0 15px;
54 | color: #858585;
55 | border-left: 4px solid #e5e5e5; }
56 |
57 | img {
58 | max-width: 100%; }
59 |
60 | a {
61 | color: #4183c4;
62 | text-decoration: none; }
63 | a:hover, a:focus {
64 | outline: 0;
65 | text-decoration: underline; }
66 | a.discouraged {
67 | text-decoration: line-through; }
68 | a.discouraged:hover, a.discouraged:focus {
69 | text-decoration: underline line-through; }
70 |
71 | table {
72 | background: #fff;
73 | width: 100%;
74 | border-collapse: collapse;
75 | border-spacing: 0;
76 | overflow: auto;
77 | margin: 0 0 0.85em; }
78 |
79 | tr:nth-child(2n) {
80 | background-color: #fbfbfb; }
81 |
82 | th, td {
83 | padding: 6px 13px;
84 | border: 1px solid #ddd; }
85 |
86 | pre {
87 | margin: 0 0 1.275em;
88 | padding: .85em 1em;
89 | overflow: auto;
90 | background: #f7f7f7;
91 | font-size: .85em;
92 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; }
93 |
94 | code {
95 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; }
96 |
97 | .item-container p > code, .item-container li > code, .top-matter p > code, .top-matter li > code {
98 | background: #f7f7f7;
99 | padding: .2em; }
100 | .item-container p > code:before, .item-container p > code:after, .item-container li > code:before, .item-container li > code:after, .top-matter p > code:before, .top-matter p > code:after, .top-matter li > code:before, .top-matter li > code:after {
101 | letter-spacing: -.2em;
102 | content: "\00a0"; }
103 |
104 | pre code {
105 | padding: 0;
106 | white-space: pre; }
107 |
108 | .content-wrapper {
109 | display: flex;
110 | flex-direction: column; }
111 | @media (min-width: 768px) {
112 | .content-wrapper {
113 | flex-direction: row; } }
114 |
115 | .header {
116 | display: flex;
117 | padding: 8px;
118 | font-size: 0.875em;
119 | background: #444;
120 | color: #999; }
121 |
122 | .header-col {
123 | margin: 0;
124 | padding: 0 8px; }
125 |
126 | .header-col--primary {
127 | flex: 1; }
128 |
129 | .header-link {
130 | color: #fff; }
131 |
132 | .header-icon {
133 | padding-right: 6px;
134 | vertical-align: -4px;
135 | height: 16px; }
136 |
137 | .breadcrumbs {
138 | font-size: 0.875em;
139 | padding: 8px 16px;
140 | margin: 0;
141 | background: #fbfbfb;
142 | border-bottom: 1px solid #ddd; }
143 |
144 | .carat {
145 | height: 10px;
146 | margin: 0 5px; }
147 |
148 | .navigation {
149 | order: 2; }
150 | @media (min-width: 768px) {
151 | .navigation {
152 | order: 1;
153 | width: 25%;
154 | max-width: 300px;
155 | padding-bottom: 64px;
156 | overflow: hidden;
157 | word-wrap: normal;
158 | background: #fbfbfb;
159 | border-right: 1px solid #ddd; } }
160 |
161 | .nav-groups {
162 | list-style-type: none;
163 | padding-left: 0; }
164 |
165 | .nav-group-name {
166 | border-bottom: 1px solid #ddd;
167 | padding: 8px 0 8px 16px; }
168 |
169 | .nav-group-name-link {
170 | color: #333; }
171 |
172 | .nav-group-tasks {
173 | margin: 8px 0;
174 | padding: 0 0 0 8px; }
175 |
176 | .nav-group-task {
177 | font-size: 1em;
178 | list-style-type: none;
179 | white-space: nowrap; }
180 |
181 | .nav-group-task-link {
182 | color: #808080; }
183 |
184 | .main-content {
185 | order: 1; }
186 | @media (min-width: 768px) {
187 | .main-content {
188 | order: 2;
189 | flex: 1;
190 | padding-bottom: 60px; } }
191 |
192 | .section {
193 | padding: 0 32px;
194 | border-bottom: 1px solid #ddd; }
195 |
196 | .section-content {
197 | max-width: 834px;
198 | margin: 0 auto;
199 | padding: 16px 0; }
200 |
201 | .section-name {
202 | color: #666;
203 | display: block; }
204 | .section-name p {
205 | margin-bottom: inherit; }
206 |
207 | .declaration .highlight {
208 | overflow-x: initial;
209 | padding: 8px 0;
210 | margin: 0;
211 | background-color: transparent;
212 | border: none; }
213 |
214 | .task-group-section {
215 | border-top: 1px solid #ddd; }
216 |
217 | .task-group {
218 | padding-top: 0px; }
219 |
220 | .task-name-container a[name]:before {
221 | content: "";
222 | display: block; }
223 |
224 | .section-name-container {
225 | position: relative; }
226 | .section-name-container .section-name-link {
227 | position: absolute;
228 | top: 0;
229 | left: 0;
230 | bottom: 0;
231 | right: 0;
232 | margin-bottom: 0; }
233 | .section-name-container .section-name {
234 | position: relative;
235 | pointer-events: none;
236 | z-index: 1; }
237 | .section-name-container .section-name a {
238 | pointer-events: auto; }
239 |
240 | .item-container {
241 | padding: 0; }
242 |
243 | .item {
244 | padding-top: 8px;
245 | width: 100%;
246 | list-style-type: none; }
247 | .item a[name]:before {
248 | content: "";
249 | display: block; }
250 | .item .token, .item .direct-link {
251 | padding-left: 3px;
252 | margin-left: 0px;
253 | font-size: 1rem; }
254 | .item .declaration-note {
255 | font-size: .85em;
256 | color: #808080;
257 | font-style: italic; }
258 |
259 | .pointer-container {
260 | border-bottom: 1px solid #ddd;
261 | left: -23px;
262 | padding-bottom: 13px;
263 | position: relative;
264 | width: 110%; }
265 |
266 | .pointer {
267 | left: 21px;
268 | top: 7px;
269 | display: block;
270 | position: absolute;
271 | width: 12px;
272 | height: 12px;
273 | border-left: 1px solid #ddd;
274 | border-top: 1px solid #ddd;
275 | background: #fff;
276 | transform: rotate(45deg); }
277 |
278 | .height-container {
279 | display: none;
280 | position: relative;
281 | width: 100%;
282 | overflow: hidden; }
283 | .height-container .section {
284 | background: #fff;
285 | border: 1px solid #ddd;
286 | border-top-width: 0;
287 | padding-top: 10px;
288 | padding-bottom: 5px;
289 | padding: 8px 16px; }
290 |
291 | .aside, .language {
292 | padding: 6px 12px;
293 | margin: 12px 0;
294 | border-left: 5px solid #dddddd;
295 | overflow-y: hidden; }
296 | .aside .aside-title, .language .aside-title {
297 | font-size: 9px;
298 | letter-spacing: 2px;
299 | text-transform: uppercase;
300 | padding-bottom: 0;
301 | margin: 0;
302 | color: #aaa;
303 | -webkit-user-select: none; }
304 | .aside p:last-child, .language p:last-child {
305 | margin-bottom: 0; }
306 |
307 | .language {
308 | border-left: 5px solid #cde9f4; }
309 | .language .aside-title {
310 | color: #4183c4; }
311 |
312 | .aside-warning, .aside-deprecated, .aside-unavailable {
313 | border-left: 5px solid #ff6666; }
314 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title {
315 | color: #ff0000; }
316 |
317 | .graybox {
318 | border-collapse: collapse;
319 | width: 100%; }
320 | .graybox p {
321 | margin: 0;
322 | word-break: break-word;
323 | min-width: 50px; }
324 | .graybox td {
325 | border: 1px solid #ddd;
326 | padding: 5px 25px 5px 10px;
327 | vertical-align: middle; }
328 | .graybox tr td:first-of-type {
329 | text-align: right;
330 | padding: 7px;
331 | vertical-align: top;
332 | word-break: normal;
333 | width: 40px; }
334 |
335 | .slightly-smaller {
336 | font-size: 0.9em; }
337 |
338 | .footer {
339 | padding: 8px 16px;
340 | background: #444;
341 | color: #ddd;
342 | font-size: 0.8em; }
343 | .footer p {
344 | margin: 8px 0; }
345 | .footer a {
346 | color: #fff; }
347 |
348 | html.dash .header, html.dash .breadcrumbs, html.dash .navigation {
349 | display: none; }
350 |
351 | html.dash .height-container {
352 | display: block; }
353 |
354 | form[role=search] input {
355 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif;
356 | font-size: 14px;
357 | line-height: 24px;
358 | padding: 0 10px;
359 | margin: 0;
360 | border: none;
361 | border-radius: 1em; }
362 | .loading form[role=search] input {
363 | background: white url(../img/spinner.gif) center right 4px no-repeat; }
364 |
365 | form[role=search] .tt-menu {
366 | margin: 0;
367 | min-width: 300px;
368 | background: #fbfbfb;
369 | color: #333;
370 | border: 1px solid #ddd; }
371 |
372 | form[role=search] .tt-highlight {
373 | font-weight: bold; }
374 |
375 | form[role=search] .tt-suggestion {
376 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif;
377 | padding: 0 8px; }
378 | form[role=search] .tt-suggestion span {
379 | display: table-cell;
380 | white-space: nowrap; }
381 | form[role=search] .tt-suggestion .doc-parent-name {
382 | width: 100%;
383 | text-align: right;
384 | font-weight: normal;
385 | font-size: 0.9em;
386 | padding-left: 16px; }
387 |
388 | form[role=search] .tt-suggestion:hover,
389 | form[role=search] .tt-suggestion.tt-cursor {
390 | cursor: pointer;
391 | background-color: #4183c4;
392 | color: #fff; }
393 |
394 | form[role=search] .tt-suggestion:hover .doc-parent-name,
395 | form[role=search] .tt-suggestion.tt-cursor .doc-parent-name {
396 | color: #fff; }
397 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIdentifier
6 | com.jazzy.
7 | CFBundleName
8 |
9 | DocSetPlatformFamily
10 |
11 | isDashDocset
12 |
13 | dashIndexFilePath
14 | index.html
15 | isJavaScriptEnabled
16 |
17 | DashDocSetFamily
18 | dashtoc
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/Classes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Classes Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
43 |
44 |
45 | Reference
46 |
47 | Classes Reference
48 |
49 |
50 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
Classes
76 |
The following classes are available globally.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
A container view controller that manages the appearance of one or more child view controller for any given state.
99 |
Overview
100 |
101 |
This class is designed to make stateful view controller programming easier. Typically in iOS development,
102 | views representing multiple states are managed in one single view controller, leading to large view controller
103 | classes that quickly become hard to work with and overlook at a glance. For instance, a view controller may
104 | display an activity indicator while a network call is performed, leaving the view controller to have to directly
105 | manipulate view hierarhy for each state. Furthermore, the state of a view controller tends to be represented by
106 | conditions that are hard to synchronize, easily becoming a source of bugs and unexpected behavior.
107 | With StateViewController
each state can be represented by one or more view controllers.
108 | This allows you to composite view controllers in self-contained classes resulting in smaller view
109 | controllers and better ability modularize your view controller code, with clear separation between states.
110 |
Subclassing notes
111 |
112 |
You must subclass StateViewController
and define a state for the view controller you are creating.
113 |
enum MyViewControllerState {
114 | case loading
115 | case ready
116 | }
117 |
118 |
119 |
Note: Your state must conform to Equatable
in order for StateViewController
to distinguish between states.
120 |
121 |
Override loadAppearanceState()
to determine which state is being represented each time this view controller
122 | is appearing on screen. In this method is appropriate to query your model layer to determine whether data needed
123 | for a certain state is available or not.
124 |
override func loadAppearanceState () -> MyViewControllerState {
125 | if model . isDataAvailable {
126 | return . ready
127 | } else {
128 | return . loading
129 | }
130 | }
131 |
132 |
133 |
To determine which content view controllers represent a particular state, you must override
134 | children(for:)
.
135 |
override func children ( for state : MyViewControllerState ) -> [ UIViewController ] {
136 | switch state {
137 | case . loading :
138 | return [ ActivityIndicatorViewController ()]
139 | case . empty :
140 | return [ myChild ]
141 | }
142 | }
143 |
144 |
145 |
Callback methods are overridable, notifying you when a state transition is being performed, and what child
146 | view controllers are being presented as a result of a state transition.
147 |
148 |
Using willTransition(to:animated:)
you should prepare view controller representing the state being transition to
149 | with the appropriate data.
150 |
override func willTransition ( to state : MyViewControllerState , animated : Bool ) {
151 | switch state {
152 | case . ready :
153 | myChild . content = myLoadedContent
154 | case . loading :
155 | break
156 | }
157 | }
158 |
159 |
160 |
Overriding didTransition(to:animated:)
is an appropriate place to invoke methods that eventually results in
161 | a state transition being requested using setNeedsTransition(to:animated:)
, as it ensures that any previous state
162 | transitions has been fully completed.
163 |
override func didTransition ( from previousState : MyViewControllerState ?, animated : Bool ) {
164 | switch state {
165 | case . ready :
166 | break
167 | case . loading :
168 | model . loadData { result in
169 | self . myLoadedContent = result
170 | self . setNeedsTransition ( to : . ready , animated : true )
171 | }
172 | }
173 | }
174 |
175 |
176 |
You may also override loadChildContainerView()
to provide a custom container view for your
177 | content view controllers, allowing you to manipulate the view hierarchy above and below the content view
178 | controller container view.
179 |
Animating state transitions
180 |
181 |
By default, no animations are performed between states. To enable animations, you have three options:
182 |
183 |
184 | Set defaultStateTransitioningCoordinator
185 | Override stateTransitionCoordinator(for:)
in your StateViewController
subclasses
186 | Conform view controllers contained in StateViewController
to StateViewControllerTransitioning
.
187 |
188 |
189 |
See more
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
205 |
206 |
207 |
208 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/Protocols.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Protocols Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
43 |
44 |
45 | Reference
46 |
47 | Protocols Reference
48 |
49 |
50 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
Protocols
76 |
The following protocols are available globally.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
View controllers can conform to this protocol to provide their desired
99 | state transitioning behaviour when contained in a StateViewController
.
100 |
101 |
See more
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/Protocols/StateViewControllerTransitioning.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | StateViewControllerTransitioning Protocol Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
43 |
44 |
45 | Reference
46 |
47 | StateViewControllerTransitioning Protocol Reference
48 |
49 |
50 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
StateViewControllerTransitioning
76 |
View controllers can conform to this protocol to provide their desired
77 | state transitioning behaviour when contained in a StateViewController
.
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
94 |
95 |
96 |
97 |
98 |
99 |
Returns the animation duration for a state transition of this view controller.
100 |
101 |
102 |
103 |
104 |
105 |
106 |
113 |
114 |
115 |
116 |
117 |
118 |
Notifies that a state transition will begin for this view controller.
119 |
120 |
121 |
122 |
123 |
124 |
125 |
132 |
133 |
134 |
135 |
136 |
137 |
Notifies that a state transition did end for this view controller.
138 |
139 |
140 |
141 |
142 |
143 |
144 |
151 |
152 |
153 |
154 |
155 |
156 |
Animations performed alongside the state transition of this view controller.
157 |
158 |
159 |
160 |
161 |
162 |
163 |
170 |
171 |
172 |
173 |
174 |
175 |
Returns the animation delay for a state transition of the provided view controller.
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css:
--------------------------------------------------------------------------------
1 | /* Credit to https://gist.github.com/wataru420/2048287 */
2 | .highlight {
3 | /* Comment */
4 | /* Error */
5 | /* Keyword */
6 | /* Operator */
7 | /* Comment.Multiline */
8 | /* Comment.Preproc */
9 | /* Comment.Single */
10 | /* Comment.Special */
11 | /* Generic.Deleted */
12 | /* Generic.Deleted.Specific */
13 | /* Generic.Emph */
14 | /* Generic.Error */
15 | /* Generic.Heading */
16 | /* Generic.Inserted */
17 | /* Generic.Inserted.Specific */
18 | /* Generic.Output */
19 | /* Generic.Prompt */
20 | /* Generic.Strong */
21 | /* Generic.Subheading */
22 | /* Generic.Traceback */
23 | /* Keyword.Constant */
24 | /* Keyword.Declaration */
25 | /* Keyword.Pseudo */
26 | /* Keyword.Reserved */
27 | /* Keyword.Type */
28 | /* Literal.Number */
29 | /* Literal.String */
30 | /* Name.Attribute */
31 | /* Name.Builtin */
32 | /* Name.Class */
33 | /* Name.Constant */
34 | /* Name.Entity */
35 | /* Name.Exception */
36 | /* Name.Function */
37 | /* Name.Namespace */
38 | /* Name.Tag */
39 | /* Name.Variable */
40 | /* Operator.Word */
41 | /* Text.Whitespace */
42 | /* Literal.Number.Float */
43 | /* Literal.Number.Hex */
44 | /* Literal.Number.Integer */
45 | /* Literal.Number.Oct */
46 | /* Literal.String.Backtick */
47 | /* Literal.String.Char */
48 | /* Literal.String.Doc */
49 | /* Literal.String.Double */
50 | /* Literal.String.Escape */
51 | /* Literal.String.Heredoc */
52 | /* Literal.String.Interpol */
53 | /* Literal.String.Other */
54 | /* Literal.String.Regex */
55 | /* Literal.String.Single */
56 | /* Literal.String.Symbol */
57 | /* Name.Builtin.Pseudo */
58 | /* Name.Variable.Class */
59 | /* Name.Variable.Global */
60 | /* Name.Variable.Instance */
61 | /* Literal.Number.Integer.Long */ }
62 | .highlight .c {
63 | color: #999988;
64 | font-style: italic; }
65 | .highlight .err {
66 | color: #a61717;
67 | background-color: #e3d2d2; }
68 | .highlight .k {
69 | color: #000000;
70 | font-weight: bold; }
71 | .highlight .o {
72 | color: #000000;
73 | font-weight: bold; }
74 | .highlight .cm {
75 | color: #999988;
76 | font-style: italic; }
77 | .highlight .cp {
78 | color: #999999;
79 | font-weight: bold; }
80 | .highlight .c1 {
81 | color: #999988;
82 | font-style: italic; }
83 | .highlight .cs {
84 | color: #999999;
85 | font-weight: bold;
86 | font-style: italic; }
87 | .highlight .gd {
88 | color: #000000;
89 | background-color: #ffdddd; }
90 | .highlight .gd .x {
91 | color: #000000;
92 | background-color: #ffaaaa; }
93 | .highlight .ge {
94 | color: #000000;
95 | font-style: italic; }
96 | .highlight .gr {
97 | color: #aa0000; }
98 | .highlight .gh {
99 | color: #999999; }
100 | .highlight .gi {
101 | color: #000000;
102 | background-color: #ddffdd; }
103 | .highlight .gi .x {
104 | color: #000000;
105 | background-color: #aaffaa; }
106 | .highlight .go {
107 | color: #888888; }
108 | .highlight .gp {
109 | color: #555555; }
110 | .highlight .gs {
111 | font-weight: bold; }
112 | .highlight .gu {
113 | color: #aaaaaa; }
114 | .highlight .gt {
115 | color: #aa0000; }
116 | .highlight .kc {
117 | color: #000000;
118 | font-weight: bold; }
119 | .highlight .kd {
120 | color: #000000;
121 | font-weight: bold; }
122 | .highlight .kp {
123 | color: #000000;
124 | font-weight: bold; }
125 | .highlight .kr {
126 | color: #000000;
127 | font-weight: bold; }
128 | .highlight .kt {
129 | color: #445588; }
130 | .highlight .m {
131 | color: #009999; }
132 | .highlight .s {
133 | color: #d14; }
134 | .highlight .na {
135 | color: #008080; }
136 | .highlight .nb {
137 | color: #0086B3; }
138 | .highlight .nc {
139 | color: #445588;
140 | font-weight: bold; }
141 | .highlight .no {
142 | color: #008080; }
143 | .highlight .ni {
144 | color: #800080; }
145 | .highlight .ne {
146 | color: #990000;
147 | font-weight: bold; }
148 | .highlight .nf {
149 | color: #990000; }
150 | .highlight .nn {
151 | color: #555555; }
152 | .highlight .nt {
153 | color: #000080; }
154 | .highlight .nv {
155 | color: #008080; }
156 | .highlight .ow {
157 | color: #000000;
158 | font-weight: bold; }
159 | .highlight .w {
160 | color: #bbbbbb; }
161 | .highlight .mf {
162 | color: #009999; }
163 | .highlight .mh {
164 | color: #009999; }
165 | .highlight .mi {
166 | color: #009999; }
167 | .highlight .mo {
168 | color: #009999; }
169 | .highlight .sb {
170 | color: #d14; }
171 | .highlight .sc {
172 | color: #d14; }
173 | .highlight .sd {
174 | color: #d14; }
175 | .highlight .s2 {
176 | color: #d14; }
177 | .highlight .se {
178 | color: #d14; }
179 | .highlight .sh {
180 | color: #d14; }
181 | .highlight .si {
182 | color: #d14; }
183 | .highlight .sx {
184 | color: #d14; }
185 | .highlight .sr {
186 | color: #009926; }
187 | .highlight .s1 {
188 | color: #d14; }
189 | .highlight .ss {
190 | color: #990073; }
191 | .highlight .bp {
192 | color: #999999; }
193 | .highlight .vc {
194 | color: #008080; }
195 | .highlight .vg {
196 | color: #008080; }
197 | .highlight .vi {
198 | color: #008080; }
199 | .highlight .il {
200 | color: #009999; }
201 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css:
--------------------------------------------------------------------------------
1 | *, *:before, *:after {
2 | box-sizing: inherit; }
3 |
4 | body {
5 | margin: 0;
6 | background: #fff;
7 | color: #333;
8 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | letter-spacing: .2px;
10 | -webkit-font-smoothing: antialiased;
11 | box-sizing: border-box; }
12 |
13 | h1 {
14 | font-size: 2rem;
15 | font-weight: 700;
16 | margin: 1.275em 0 0.6em; }
17 |
18 | h2 {
19 | font-size: 1.75rem;
20 | font-weight: 700;
21 | margin: 1.275em 0 0.3em; }
22 |
23 | h3 {
24 | font-size: 1.5rem;
25 | font-weight: 700;
26 | margin: 1em 0 0.3em; }
27 |
28 | h4 {
29 | font-size: 1.25rem;
30 | font-weight: 700;
31 | margin: 1.275em 0 0.85em; }
32 |
33 | h5 {
34 | font-size: 1rem;
35 | font-weight: 700;
36 | margin: 1.275em 0 0.85em; }
37 |
38 | h6 {
39 | font-size: 1rem;
40 | font-weight: 700;
41 | margin: 1.275em 0 0.85em;
42 | color: #777; }
43 |
44 | p {
45 | margin: 0 0 1em; }
46 |
47 | ul, ol {
48 | padding: 0 0 0 2em;
49 | margin: 0 0 0.85em; }
50 |
51 | blockquote {
52 | margin: 0 0 0.85em;
53 | padding: 0 15px;
54 | color: #858585;
55 | border-left: 4px solid #e5e5e5; }
56 |
57 | img {
58 | max-width: 100%; }
59 |
60 | a {
61 | color: #4183c4;
62 | text-decoration: none; }
63 | a:hover, a:focus {
64 | outline: 0;
65 | text-decoration: underline; }
66 | a.discouraged {
67 | text-decoration: line-through; }
68 | a.discouraged:hover, a.discouraged:focus {
69 | text-decoration: underline line-through; }
70 |
71 | table {
72 | background: #fff;
73 | width: 100%;
74 | border-collapse: collapse;
75 | border-spacing: 0;
76 | overflow: auto;
77 | margin: 0 0 0.85em; }
78 |
79 | tr:nth-child(2n) {
80 | background-color: #fbfbfb; }
81 |
82 | th, td {
83 | padding: 6px 13px;
84 | border: 1px solid #ddd; }
85 |
86 | pre {
87 | margin: 0 0 1.275em;
88 | padding: .85em 1em;
89 | overflow: auto;
90 | background: #f7f7f7;
91 | font-size: .85em;
92 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; }
93 |
94 | code {
95 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; }
96 |
97 | .item-container p > code, .item-container li > code, .top-matter p > code, .top-matter li > code {
98 | background: #f7f7f7;
99 | padding: .2em; }
100 | .item-container p > code:before, .item-container p > code:after, .item-container li > code:before, .item-container li > code:after, .top-matter p > code:before, .top-matter p > code:after, .top-matter li > code:before, .top-matter li > code:after {
101 | letter-spacing: -.2em;
102 | content: "\00a0"; }
103 |
104 | pre code {
105 | padding: 0;
106 | white-space: pre; }
107 |
108 | .content-wrapper {
109 | display: flex;
110 | flex-direction: column; }
111 | @media (min-width: 768px) {
112 | .content-wrapper {
113 | flex-direction: row; } }
114 |
115 | .header {
116 | display: flex;
117 | padding: 8px;
118 | font-size: 0.875em;
119 | background: #444;
120 | color: #999; }
121 |
122 | .header-col {
123 | margin: 0;
124 | padding: 0 8px; }
125 |
126 | .header-col--primary {
127 | flex: 1; }
128 |
129 | .header-link {
130 | color: #fff; }
131 |
132 | .header-icon {
133 | padding-right: 6px;
134 | vertical-align: -4px;
135 | height: 16px; }
136 |
137 | .breadcrumbs {
138 | font-size: 0.875em;
139 | padding: 8px 16px;
140 | margin: 0;
141 | background: #fbfbfb;
142 | border-bottom: 1px solid #ddd; }
143 |
144 | .carat {
145 | height: 10px;
146 | margin: 0 5px; }
147 |
148 | .navigation {
149 | order: 2; }
150 | @media (min-width: 768px) {
151 | .navigation {
152 | order: 1;
153 | width: 25%;
154 | max-width: 300px;
155 | padding-bottom: 64px;
156 | overflow: hidden;
157 | word-wrap: normal;
158 | background: #fbfbfb;
159 | border-right: 1px solid #ddd; } }
160 |
161 | .nav-groups {
162 | list-style-type: none;
163 | padding-left: 0; }
164 |
165 | .nav-group-name {
166 | border-bottom: 1px solid #ddd;
167 | padding: 8px 0 8px 16px; }
168 |
169 | .nav-group-name-link {
170 | color: #333; }
171 |
172 | .nav-group-tasks {
173 | margin: 8px 0;
174 | padding: 0 0 0 8px; }
175 |
176 | .nav-group-task {
177 | font-size: 1em;
178 | list-style-type: none;
179 | white-space: nowrap; }
180 |
181 | .nav-group-task-link {
182 | color: #808080; }
183 |
184 | .main-content {
185 | order: 1; }
186 | @media (min-width: 768px) {
187 | .main-content {
188 | order: 2;
189 | flex: 1;
190 | padding-bottom: 60px; } }
191 |
192 | .section {
193 | padding: 0 32px;
194 | border-bottom: 1px solid #ddd; }
195 |
196 | .section-content {
197 | max-width: 834px;
198 | margin: 0 auto;
199 | padding: 16px 0; }
200 |
201 | .section-name {
202 | color: #666;
203 | display: block; }
204 | .section-name p {
205 | margin-bottom: inherit; }
206 |
207 | .declaration .highlight {
208 | overflow-x: initial;
209 | padding: 8px 0;
210 | margin: 0;
211 | background-color: transparent;
212 | border: none; }
213 |
214 | .task-group-section {
215 | border-top: 1px solid #ddd; }
216 |
217 | .task-group {
218 | padding-top: 0px; }
219 |
220 | .task-name-container a[name]:before {
221 | content: "";
222 | display: block; }
223 |
224 | .section-name-container {
225 | position: relative; }
226 | .section-name-container .section-name-link {
227 | position: absolute;
228 | top: 0;
229 | left: 0;
230 | bottom: 0;
231 | right: 0;
232 | margin-bottom: 0; }
233 | .section-name-container .section-name {
234 | position: relative;
235 | pointer-events: none;
236 | z-index: 1; }
237 | .section-name-container .section-name a {
238 | pointer-events: auto; }
239 |
240 | .item-container {
241 | padding: 0; }
242 |
243 | .item {
244 | padding-top: 8px;
245 | width: 100%;
246 | list-style-type: none; }
247 | .item a[name]:before {
248 | content: "";
249 | display: block; }
250 | .item .token, .item .direct-link {
251 | padding-left: 3px;
252 | margin-left: 0px;
253 | font-size: 1rem; }
254 | .item .declaration-note {
255 | font-size: .85em;
256 | color: #808080;
257 | font-style: italic; }
258 |
259 | .pointer-container {
260 | border-bottom: 1px solid #ddd;
261 | left: -23px;
262 | padding-bottom: 13px;
263 | position: relative;
264 | width: 110%; }
265 |
266 | .pointer {
267 | left: 21px;
268 | top: 7px;
269 | display: block;
270 | position: absolute;
271 | width: 12px;
272 | height: 12px;
273 | border-left: 1px solid #ddd;
274 | border-top: 1px solid #ddd;
275 | background: #fff;
276 | transform: rotate(45deg); }
277 |
278 | .height-container {
279 | display: none;
280 | position: relative;
281 | width: 100%;
282 | overflow: hidden; }
283 | .height-container .section {
284 | background: #fff;
285 | border: 1px solid #ddd;
286 | border-top-width: 0;
287 | padding-top: 10px;
288 | padding-bottom: 5px;
289 | padding: 8px 16px; }
290 |
291 | .aside, .language {
292 | padding: 6px 12px;
293 | margin: 12px 0;
294 | border-left: 5px solid #dddddd;
295 | overflow-y: hidden; }
296 | .aside .aside-title, .language .aside-title {
297 | font-size: 9px;
298 | letter-spacing: 2px;
299 | text-transform: uppercase;
300 | padding-bottom: 0;
301 | margin: 0;
302 | color: #aaa;
303 | -webkit-user-select: none; }
304 | .aside p:last-child, .language p:last-child {
305 | margin-bottom: 0; }
306 |
307 | .language {
308 | border-left: 5px solid #cde9f4; }
309 | .language .aside-title {
310 | color: #4183c4; }
311 |
312 | .aside-warning, .aside-deprecated, .aside-unavailable {
313 | border-left: 5px solid #ff6666; }
314 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title {
315 | color: #ff0000; }
316 |
317 | .graybox {
318 | border-collapse: collapse;
319 | width: 100%; }
320 | .graybox p {
321 | margin: 0;
322 | word-break: break-word;
323 | min-width: 50px; }
324 | .graybox td {
325 | border: 1px solid #ddd;
326 | padding: 5px 25px 5px 10px;
327 | vertical-align: middle; }
328 | .graybox tr td:first-of-type {
329 | text-align: right;
330 | padding: 7px;
331 | vertical-align: top;
332 | word-break: normal;
333 | width: 40px; }
334 |
335 | .slightly-smaller {
336 | font-size: 0.9em; }
337 |
338 | .footer {
339 | padding: 8px 16px;
340 | background: #444;
341 | color: #ddd;
342 | font-size: 0.8em; }
343 | .footer p {
344 | margin: 8px 0; }
345 | .footer a {
346 | color: #fff; }
347 |
348 | html.dash .header, html.dash .breadcrumbs, html.dash .navigation {
349 | display: none; }
350 |
351 | html.dash .height-container {
352 | display: block; }
353 |
354 | form[role=search] input {
355 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif;
356 | font-size: 14px;
357 | line-height: 24px;
358 | padding: 0 10px;
359 | margin: 0;
360 | border: none;
361 | border-radius: 1em; }
362 | .loading form[role=search] input {
363 | background: white url(../img/spinner.gif) center right 4px no-repeat; }
364 |
365 | form[role=search] .tt-menu {
366 | margin: 0;
367 | min-width: 300px;
368 | background: #fbfbfb;
369 | color: #333;
370 | border: 1px solid #ddd; }
371 |
372 | form[role=search] .tt-highlight {
373 | font-weight: bold; }
374 |
375 | form[role=search] .tt-suggestion {
376 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif;
377 | padding: 0 8px; }
378 | form[role=search] .tt-suggestion span {
379 | display: table-cell;
380 | white-space: nowrap; }
381 | form[role=search] .tt-suggestion .doc-parent-name {
382 | width: 100%;
383 | text-align: right;
384 | font-weight: normal;
385 | font-size: 0.9em;
386 | padding-left: 16px; }
387 |
388 | form[role=search] .tt-suggestion:hover,
389 | form[role=search] .tt-suggestion.tt-cursor {
390 | cursor: pointer;
391 | background-color: #4183c4;
392 | color: #fff; }
393 |
394 | form[role=search] .tt-suggestion:hover .doc-parent-name,
395 | form[role=search] .tt-suggestion.tt-cursor .doc-parent-name {
396 | color: #fff; }
397 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/docsets/.docset/Contents/Resources/Documents/img/spinner.gif
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js:
--------------------------------------------------------------------------------
1 | window.jazzy = {'docset': false}
2 | if (typeof window.dash != 'undefined') {
3 | document.documentElement.className += ' dash'
4 | window.jazzy.docset = true
5 | }
6 | if (navigator.userAgent.match(/xcode/i)) {
7 | document.documentElement.className += ' xcode'
8 | window.jazzy.docset = true
9 | }
10 |
11 | function toggleItem($link, $content) {
12 | var animationDuration = 300;
13 | $link.toggleClass('token-open');
14 | $content.slideToggle(animationDuration);
15 | }
16 |
17 | function itemLinkToContent($link) {
18 | return $link.parent().parent().next();
19 | }
20 |
21 | // On doc load + hash-change, open any targetted item
22 | function openCurrentItemIfClosed() {
23 | if (window.jazzy.docset) {
24 | return;
25 | }
26 | var $link = $(`.token[href="${location.hash}"]`);
27 | $content = itemLinkToContent($link);
28 | if ($content.is(':hidden')) {
29 | toggleItem($link, $content);
30 | }
31 | }
32 |
33 | $(openCurrentItemIfClosed);
34 | $(window).on('hashchange', openCurrentItemIfClosed);
35 |
36 | // On item link ('token') click, toggle its discussion
37 | $('.token').on('click', function(event) {
38 | if (window.jazzy.docset) {
39 | return;
40 | }
41 | var $link = $(this);
42 | toggleItem($link, itemLinkToContent($link));
43 |
44 | // Keeps the document from jumping to the hash.
45 | var href = $link.attr('href');
46 | if (history.pushState) {
47 | history.pushState({}, '', href);
48 | } else {
49 | location.hash = href;
50 | }
51 | event.preventDefault();
52 | });
53 |
54 | // Clicks on links to the current, closed, item need to open the item
55 | $("a:not('.token')").on('click', function() {
56 | if (location == this.href) {
57 | openCurrentItemIfClosed();
58 | }
59 | });
60 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.search.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | var $typeahead = $('[data-typeahead]');
3 | var $form = $typeahead.parents('form');
4 | var searchURL = $form.attr('action');
5 |
6 | function displayTemplate(result) {
7 | return result.name;
8 | }
9 |
10 | function suggestionTemplate(result) {
11 | var t = '';
12 | t += '' + result.name + ' ';
13 | if (result.parent_name) {
14 | t += '' + result.parent_name + ' ';
15 | }
16 | t += '
';
17 | return t;
18 | }
19 |
20 | $typeahead.one('focus', function() {
21 | $form.addClass('loading');
22 |
23 | $.getJSON(searchURL).then(function(searchData) {
24 | const searchIndex = lunr(function() {
25 | this.ref('url');
26 | this.field('name');
27 | this.field('abstract');
28 | for (const [url, doc] of Object.entries(searchData)) {
29 | this.add({url: url, name: doc.name, abstract: doc.abstract});
30 | }
31 | });
32 |
33 | $typeahead.typeahead(
34 | {
35 | highlight: true,
36 | minLength: 3,
37 | autoselect: true
38 | },
39 | {
40 | limit: 10,
41 | display: displayTemplate,
42 | templates: { suggestion: suggestionTemplate },
43 | source: function(query, sync) {
44 | const lcSearch = query.toLowerCase();
45 | const results = searchIndex.query(function(q) {
46 | q.term(lcSearch, { boost: 100 });
47 | q.term(lcSearch, {
48 | boost: 10,
49 | wildcard: lunr.Query.wildcard.TRAILING
50 | });
51 | }).map(function(result) {
52 | var doc = searchData[result.ref];
53 | doc.url = result.ref;
54 | return doc;
55 | });
56 | sync(results);
57 | }
58 | }
59 | );
60 | $form.removeClass('loading');
61 | $typeahead.trigger('focus');
62 | });
63 | });
64 |
65 | var baseURL = searchURL.slice(0, -"search.json".length);
66 |
67 | $typeahead.on('typeahead:select', function(e, result) {
68 | window.location = baseURL + result.url;
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/Documents/search.json:
--------------------------------------------------------------------------------
1 | {"Protocols/StateViewControllerTransitioning.html#/stateTransitionDuration(isAppearing:)":{"name":"stateTransitionDuration(isAppearing:)","abstract":"Returns the animation duration for a state transition of this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/stateTransitionWillBegin(isAppearing:)":{"name":"stateTransitionWillBegin(isAppearing:)","abstract":"Notifies that a state transition will begin for this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/stateTransitionDidEnd(isAppearing:)":{"name":"stateTransitionDidEnd(isAppearing:)","abstract":"Notifies that a state transition did end for this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/animateAlongsideStateTransition(isAppearing:)":{"name":"animateAlongsideStateTransition(isAppearing:)","abstract":"Animations performed alongside the state transition of this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/stateTransitionDelay(isAppearing:)":{"name":"stateTransitionDelay(isAppearing:)","abstract":"Returns the animation delay for a state transition of the provided view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html":{"name":"StateViewControllerTransitioning","abstract":"View controllers can conform to this protocol to provide their desired"},"Classes/StateViewController.html#/childForStatusBarStyle":{"name":"childForStatusBarStyle","parent_name":"StateViewController"},"Classes/StateViewController.html#/childForStatusBarHidden":{"name":"childForStatusBarHidden","parent_name":"StateViewController"},"Classes/StateViewController.html#/isTransitioningBetweenStates":{"name":"isTransitioningBetweenStates","abstract":"
Indicates whether the view controller currently is transitioning between states.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/currentState":{"name":"currentState","abstract":"Indicates the current state, or invokes loadAppearanceState()
is a current state transition has not","parent_name":"StateViewController"},"Classes/StateViewController.html#/hasDeterminedState":{"name":"hasDeterminedState","abstract":"
Indicates whether the state of this view controller has been determined.","parent_name":"StateViewController"},"Classes/StateViewController.html#/loadAppearanceState()":{"name":"loadAppearanceState()","abstract":"
Loads a state that should represent this view controller immediately as this view controller","parent_name":"StateViewController"},"Classes/StateViewController.html#/setNeedsStateTransition(to:animated:)":{"name":"setNeedsStateTransition(to:animated:)","abstract":"
Notifies the state view controller that a new state is needed.","parent_name":"StateViewController"},"Classes/StateViewController.html#/children(for:)":{"name":"children(for:)","abstract":"
Returns an array of content view controllers representing a state.","parent_name":"StateViewController"},"Classes/StateViewController.html#/childContainerView":{"name":"childContainerView","abstract":"
Container view placed directly in the StateViewController
s view.","parent_name":"StateViewController"},"Classes/StateViewController.html#/loadChildContainerView()":{"name":"loadChildContainerView()","abstract":"
","parent_name":"StateViewController"},"Classes/StateViewController.html#/willTransition(to:animated:)":{"name":"willTransition(to:animated:)","abstract":"
Notifies the view controller that a state transition is to be performed.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/didTransition(from:animated:)":{"name":"didTransition(from:animated:)","abstract":"
Notifies the view controller that it has finished transitioning to a new state.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/childWillAppear(_:animated:)":{"name":"childWillAppear(_:animated:)","abstract":"
Notifies the view controller that a content view controller will appear.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/childDidAppear(_:animated:)":{"name":"childDidAppear(_:animated:)","abstract":"
Notifies the view controller that a content view controller did appear.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/childWillDisappear(_:animated:)":{"name":"childWillDisappear(_:animated:)","abstract":"
Notifies the view controller that a content view controller will disappear.","parent_name":"StateViewController"},"Classes/StateViewController.html#/childDidDisappear(_:animated:)":{"name":"childDidDisappear(_:animated:)","abstract":"
Notifies the view controller that a content view controller did disappear.
","parent_name":"StateViewController"},"Classes/StateViewController.html":{"name":"StateViewController","abstract":"
A container view controller that manages the appearance of one or more child view controller for any given state.
"},"Classes.html":{"name":"Classes","abstract":"
The following classes are available globally.
"},"Protocols.html":{"name":"Protocols","abstract":"
The following protocols are available globally.
"}}
--------------------------------------------------------------------------------
/docs/docsets/.docset/Contents/Resources/docSet.dsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/docsets/.docset/Contents/Resources/docSet.dsidx
--------------------------------------------------------------------------------
/docs/docsets/.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/docsets/.tgz
--------------------------------------------------------------------------------
/docs/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/img/carat.png
--------------------------------------------------------------------------------
/docs/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/img/dash.png
--------------------------------------------------------------------------------
/docs/img/gh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/img/gh.png
--------------------------------------------------------------------------------
/docs/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/docs/img/spinner.gif
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
42 |
43 |
44 | Reference
45 |
46 | Reference
47 |
48 |
49 |
50 |
51 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
StateViewController
77 |
78 |
When creating rich view controllers, a single view controller class is often tasked with managing the appearance of many other views, controls, and other user interface elements based on a state. That state, in turn, is often derived from multiple sources that need to be synchronized to correctly represent a single reliable state. Usually the end result is known as the Massive View Controller problem, often solved by attempts to abandon the MVC pattern, the primary design pattern in UIKit. While other patterns, such as MVVM or MVP , can solve some issues, going with the grain rather than against makes interacting with UIKit more accommodating.
79 |
80 |
This repository houses a UIViewController
subclass, enabling modularization and decoupling of view controllers, reducing the size of individual view controllers substantially, without the need for abandoning MVC as a design pattern.
81 |
Requirements
82 |
83 |
84 | iOS 8.0+
85 | tvOS 9.0+
86 |
87 |
Overview
88 |
89 |
StateViewController
is a container view controller that presents one or more view controllers for any given state that you define, such as loading
, list
, or editing
. It manages the appearance cycles of each child view controller, making sure that the view life cycle of the child view controllers are intact and in order, notifying you about state transitions and which child view controllers are about to appear or disappear from the view hierarchy. This allows you to compose multiple view controllers and re-use them throughout the app. The state view controller also provides extensive support for animating the transition between states.
90 |
91 |
Which view controller(s) are visible on screen is dictated by children(for:)
.
92 |
State transitions during appearance transition
93 |
94 |
When presented on screen, the a state view controller requires an initial state as a starting point. During its appearance transition, the loadAppearanceState()
method is invoked to query the state appropriate to transition to as the state view controller appears on screen.
95 | If the appearance transition is animated, the state transition animation is respected, and target child view controllers have the option to appear asynchronously. If the appearance transition is not animated, all child view controllers are immediately placed on screen.
96 |
97 |
loadAppearanceState()
must execute synchronously, and is a good place to query any persistence layer for available data, determining whether a final state is ready.
98 |
99 |
100 |
State transitions while on screen
101 |
102 |
When on-screen, invoking setNeedsTransition:to:
will trigger a transition from the current state to the target state. A common practice is to have the transition from one state to another to trigger an an asynchronous operation (such as a network call), which upon completion, requests a third state based on the success of the asynchronous operation.
103 |
104 |
105 |
Documentation
106 |
107 |
The source code documentation can be found here .
108 |
Installation
109 |
110 |
This module is available via Carthage . Modify your Cartfile to include StateViewController
:
111 |
github "davidask/StateViewController"
112 |
113 |
Usage
114 |
import StateViewController
115 |
116 |
Subclassing StateViewController
117 |
118 |
To use StateViewController
you must override it. The class specifies a generic with a subtype of State
. The state type can be designed to house the actual model data required by your view controller, but that’s an optional design decision. For instance, you can create a state that simply determines an abstract state:
119 |
enum MyState {
120 | case loading
121 | case ready
122 | case error
123 | }
124 |
125 |
126 |
Or, you can define a state which in itself contains model data:
127 |
enum MyState {
128 | case loading
129 | case ready ( MyModel )
130 | case error ( Error )
131 | }
132 |
133 |
134 |
Once you have a state, create a subclass of StateViewController
.
135 |
class MyStateViewController : StateViewController < MyState >
136 |
137 |
138 |
Each time StateViewController
is about to appear on screen it will call its loadAppearanceState()
method. This method returns a state which should be ready for display as soon as the view controller is on screen. Override this method to determine what state is appropriate to display immediately, depending on cached data or the contents of your database.
139 |
override func loadAppearanceState () -> MyState {
140 | if let myModel = MyCache . cachedModel {
141 | return . ready ( myModel )
142 | } else {
143 | return . loading
144 | }
145 | }
146 |
147 |
148 |
Each state can be represented by zero or more view controllers. To provide which view controllers are visible for what state, override children(for:)
.
149 |
override func children ( for state : MyState ) -> [ UIViewController ] {
150 | switch state {
151 | case . loading :
152 | return [ ActivityIndicatorViewController ()]
153 | case . ready :
154 | return [ myTableViewController ]
155 | case . error :
156 | return [ ErrorViewController ()]
157 | }
158 | }
159 |
160 |
161 |
You receive callbacks for when a state transition will begin, and when it has finished.
162 | willTransition(to:animated:)
is a good place to prepare your child view controllers for appearance.
163 |
override func willTransition ( to nextState : MyState , animated : Bool ) {
164 | switch nextState {
165 | case . ready ( let model ):
166 | navigationItem . setRightBarButton ( myBarButtonItem , animated : animated )
167 | myTableViewController . setRows ( model . objects )
168 | default :
169 | navigationItem . setRightBarButton ( nil , animated : animated )
170 | }
171 | }
172 |
173 |
174 |
When didTransition(from:animated:)
is called, a state transition has finished successfully. This is a good time to invoke other methods which in turn will trigger another state transition.
175 |
override func didTransition ( from previousState : State ?, animated : Bool ) {
176 | switch currentState {
177 | case . loading :
178 | fetchData { model in
179 | self . setNeedsTransition ( to : . ready ( model ), animated : true )
180 | }
181 | defualt :
182 | break
183 | }
184 | }
185 |
186 |
187 |
Your StateViewController
is now ready, and will switch between view controllers depending on state. Using setNeedsTransition(:to:animated:)
you can transition between various states during the life cycle of your state view controller subclass.
188 |
189 |
Multiple other callbacks are available for determining when a child view controller is appearing or disappearing. Please reference the documentation or the Example .
190 |
Providing transitions between child view controllers
191 |
192 |
Child view controllers of StateViewController
conforming to the StateViewControllerTransitioning
protocol can individually control their own transition. The available methods provide functionality for:
193 |
194 |
195 | Specifying duration and delayed start of an animated transition
196 | Preparing the view controller for presentation
197 | Performing animations along side an animated transition
198 | Performing operations after a transition
199 |
200 |
Example
201 |
202 |
In the example application included in this project the state view controller switches between two view controllers. Firstly, it displays and animates the transition of an activity indicator view controller while a network call is being performed. Once the network call is successfully completed it transitions into a state displaying a table view with the loaded content.
203 |
Contribute
204 |
205 |
Please feel welcome contributing to StateViewController , check the LICENSE
file for more info.
206 |
Credits
207 |
208 |
David Ask
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
220 |
221 |
222 |
223 |
--------------------------------------------------------------------------------
/docs/js/jazzy.js:
--------------------------------------------------------------------------------
1 | window.jazzy = {'docset': false}
2 | if (typeof window.dash != 'undefined') {
3 | document.documentElement.className += ' dash'
4 | window.jazzy.docset = true
5 | }
6 | if (navigator.userAgent.match(/xcode/i)) {
7 | document.documentElement.className += ' xcode'
8 | window.jazzy.docset = true
9 | }
10 |
11 | function toggleItem($link, $content) {
12 | var animationDuration = 300;
13 | $link.toggleClass('token-open');
14 | $content.slideToggle(animationDuration);
15 | }
16 |
17 | function itemLinkToContent($link) {
18 | return $link.parent().parent().next();
19 | }
20 |
21 | // On doc load + hash-change, open any targetted item
22 | function openCurrentItemIfClosed() {
23 | if (window.jazzy.docset) {
24 | return;
25 | }
26 | var $link = $(`.token[href="${location.hash}"]`);
27 | $content = itemLinkToContent($link);
28 | if ($content.is(':hidden')) {
29 | toggleItem($link, $content);
30 | }
31 | }
32 |
33 | $(openCurrentItemIfClosed);
34 | $(window).on('hashchange', openCurrentItemIfClosed);
35 |
36 | // On item link ('token') click, toggle its discussion
37 | $('.token').on('click', function(event) {
38 | if (window.jazzy.docset) {
39 | return;
40 | }
41 | var $link = $(this);
42 | toggleItem($link, itemLinkToContent($link));
43 |
44 | // Keeps the document from jumping to the hash.
45 | var href = $link.attr('href');
46 | if (history.pushState) {
47 | history.pushState({}, '', href);
48 | } else {
49 | location.hash = href;
50 | }
51 | event.preventDefault();
52 | });
53 |
54 | // Clicks on links to the current, closed, item need to open the item
55 | $("a:not('.token')").on('click', function() {
56 | if (location == this.href) {
57 | openCurrentItemIfClosed();
58 | }
59 | });
60 |
--------------------------------------------------------------------------------
/docs/js/jazzy.search.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | var $typeahead = $('[data-typeahead]');
3 | var $form = $typeahead.parents('form');
4 | var searchURL = $form.attr('action');
5 |
6 | function displayTemplate(result) {
7 | return result.name;
8 | }
9 |
10 | function suggestionTemplate(result) {
11 | var t = '';
12 | t += '' + result.name + ' ';
13 | if (result.parent_name) {
14 | t += '' + result.parent_name + ' ';
15 | }
16 | t += '
';
17 | return t;
18 | }
19 |
20 | $typeahead.one('focus', function() {
21 | $form.addClass('loading');
22 |
23 | $.getJSON(searchURL).then(function(searchData) {
24 | const searchIndex = lunr(function() {
25 | this.ref('url');
26 | this.field('name');
27 | this.field('abstract');
28 | for (const [url, doc] of Object.entries(searchData)) {
29 | this.add({url: url, name: doc.name, abstract: doc.abstract});
30 | }
31 | });
32 |
33 | $typeahead.typeahead(
34 | {
35 | highlight: true,
36 | minLength: 3,
37 | autoselect: true
38 | },
39 | {
40 | limit: 10,
41 | display: displayTemplate,
42 | templates: { suggestion: suggestionTemplate },
43 | source: function(query, sync) {
44 | const lcSearch = query.toLowerCase();
45 | const results = searchIndex.query(function(q) {
46 | q.term(lcSearch, { boost: 100 });
47 | q.term(lcSearch, {
48 | boost: 10,
49 | wildcard: lunr.Query.wildcard.TRAILING
50 | });
51 | }).map(function(result) {
52 | var doc = searchData[result.ref];
53 | doc.url = result.ref;
54 | return doc;
55 | });
56 | sync(results);
57 | }
58 | }
59 | );
60 | $form.removeClass('loading');
61 | $typeahead.trigger('focus');
62 | });
63 | });
64 |
65 | var baseURL = searchURL.slice(0, -"search.json".length);
66 |
67 | $typeahead.on('typeahead:select', function(e, result) {
68 | window.location = baseURL + result.url;
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/docs/search.json:
--------------------------------------------------------------------------------
1 | {"Protocols/StateViewControllerTransitioning.html#/stateTransitionDuration(isAppearing:)":{"name":"stateTransitionDuration(isAppearing:)","abstract":"Returns the animation duration for a state transition of this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/stateTransitionWillBegin(isAppearing:)":{"name":"stateTransitionWillBegin(isAppearing:)","abstract":"Notifies that a state transition will begin for this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/stateTransitionDidEnd(isAppearing:)":{"name":"stateTransitionDidEnd(isAppearing:)","abstract":"Notifies that a state transition did end for this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/animateAlongsideStateTransition(isAppearing:)":{"name":"animateAlongsideStateTransition(isAppearing:)","abstract":"Animations performed alongside the state transition of this view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html#/stateTransitionDelay(isAppearing:)":{"name":"stateTransitionDelay(isAppearing:)","abstract":"Returns the animation delay for a state transition of the provided view controller.
","parent_name":"StateViewControllerTransitioning"},"Protocols/StateViewControllerTransitioning.html":{"name":"StateViewControllerTransitioning","abstract":"View controllers can conform to this protocol to provide their desired"},"Classes/StateViewController.html#/childForStatusBarStyle":{"name":"childForStatusBarStyle","parent_name":"StateViewController"},"Classes/StateViewController.html#/childForStatusBarHidden":{"name":"childForStatusBarHidden","parent_name":"StateViewController"},"Classes/StateViewController.html#/isTransitioningBetweenStates":{"name":"isTransitioningBetweenStates","abstract":"
Indicates whether the view controller currently is transitioning between states.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/currentState":{"name":"currentState","abstract":"Indicates the current state, or invokes loadAppearanceState()
is a current state transition has not","parent_name":"StateViewController"},"Classes/StateViewController.html#/hasDeterminedState":{"name":"hasDeterminedState","abstract":"
Indicates whether the state of this view controller has been determined.","parent_name":"StateViewController"},"Classes/StateViewController.html#/loadAppearanceState()":{"name":"loadAppearanceState()","abstract":"
Loads a state that should represent this view controller immediately as this view controller","parent_name":"StateViewController"},"Classes/StateViewController.html#/setNeedsStateTransition(to:animated:)":{"name":"setNeedsStateTransition(to:animated:)","abstract":"
Notifies the state view controller that a new state is needed.","parent_name":"StateViewController"},"Classes/StateViewController.html#/children(for:)":{"name":"children(for:)","abstract":"
Returns an array of content view controllers representing a state.","parent_name":"StateViewController"},"Classes/StateViewController.html#/childContainerView":{"name":"childContainerView","abstract":"
Container view placed directly in the StateViewController
s view.","parent_name":"StateViewController"},"Classes/StateViewController.html#/loadChildContainerView()":{"name":"loadChildContainerView()","abstract":"
","parent_name":"StateViewController"},"Classes/StateViewController.html#/willTransition(to:animated:)":{"name":"willTransition(to:animated:)","abstract":"
Notifies the view controller that a state transition is to be performed.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/didTransition(from:animated:)":{"name":"didTransition(from:animated:)","abstract":"
Notifies the view controller that it has finished transitioning to a new state.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/childWillAppear(_:animated:)":{"name":"childWillAppear(_:animated:)","abstract":"
Notifies the view controller that a content view controller will appear.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/childDidAppear(_:animated:)":{"name":"childDidAppear(_:animated:)","abstract":"
Notifies the view controller that a content view controller did appear.
","parent_name":"StateViewController"},"Classes/StateViewController.html#/childWillDisappear(_:animated:)":{"name":"childWillDisappear(_:animated:)","abstract":"
Notifies the view controller that a content view controller will disappear.","parent_name":"StateViewController"},"Classes/StateViewController.html#/childDidDisappear(_:animated:)":{"name":"childDidDisappear(_:animated:)","abstract":"
Notifies the view controller that a content view controller did disappear.
","parent_name":"StateViewController"},"Classes/StateViewController.html":{"name":"StateViewController","abstract":"
A container view controller that manages the appearance of one or more child view controller for any given state.
"},"Classes.html":{"name":"Classes","abstract":"
The following classes are available globally.
"},"Protocols.html":{"name":"Protocols","abstract":"
The following protocols are available globally.
"}}
--------------------------------------------------------------------------------
/docs/undocumented.json:
--------------------------------------------------------------------------------
1 | {
2 | "warnings": [
3 |
4 | ],
5 | "source_directory": "/Users/davidask/Github/davidask/StateViewController"
6 | }
--------------------------------------------------------------------------------
/images/between-lifecycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/images/between-lifecycle.png
--------------------------------------------------------------------------------
/images/during-lifecycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidask/StateViewController/98f183d417ea6c8a74fa2bb297e0d574c134358b/images/during-lifecycle.png
--------------------------------------------------------------------------------