-
Notifications
You must be signed in to change notification settings - Fork 1
/
UIViewController+Snippets.swift
241 lines (219 loc) · 9.48 KB
/
UIViewController+Snippets.swift
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Snippets UIViewController+Snippets.swift
//
// Copyright © 2015, 2016, Roy Ratcliffe, Pioneering Software, United Kingdom
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
//------------------------------------------------------------------------------
import UIKit
extension UIViewController {
/// Displays the given content within this container.
///
/// - parameter content: Content view controller to display within this
/// container's content hierarchy.
///
/// - parameter frameForView: Optional capture describing how to set up the
/// new content's frame. If not given, the content's frame remains
/// unchanged, and therefore assumes that layout constraints will
/// determine the view's position and size within the new container. The
/// capture's argument takes this container's view and answers the new
/// content's frame within that view.
///
/// Adds the other view controller's view to this container's view
/// hierarchy. Automatically removes the content from any other container
/// before adding it to this container, invoking
/// `willMoveToParentViewController` on the content view controller.
///
/// View controllers, amongst other things, can operate as containers for
/// other content controllers; they have a self-referencing
/// container-content relationship. Content controllers become children
/// belonging to a parent container. Content views join the container's view
/// hierarchy.
public func display(content: UIViewController,
frameForView: ((UIView) -> CGRect)? = nil) {
addChildViewController(content)
if let frameForView = frameForView {
content.view.frame = frameForView(view)
}
view.addSubview(content.view)
content.didMove(toParentViewController: self)
}
public func hide(content: UIViewController) {
content.willMove(toParentViewController: nil)
content.view.removeFromSuperview()
content.removeFromParentViewController()
}
/// Cycles from an existing content view controller to another new content
/// view controller. Handles the will- and did-move-to-parent messages for
/// both the incoming and outgoing view controllers. Removes the existing
/// content view controller from the parent view controller when animation
/// finishes.
public func cycle(from content: UIViewController,
// swiftlint:disable:previous function_parameter_count
to newContent: UIViewController,
duration: TimeInterval,
options: UIViewAnimationOptions,
newContentStartFrame: CGRect?,
contentEndFrame: CGRect?) {
content.willMove(toParentViewController: nil)
addChildViewController(newContent)
if let frame = newContentStartFrame {
newContent.view.frame = frame
}
transition(from: content,
to: newContent,
duration: duration,
options: options,
animations: {
newContent.view.frame = content.view.frame
if let frame = contentEndFrame {
content.view.frame = frame
}
}) { finished in
content.removeFromParentViewController()
newContent.didMove(toParentViewController: self)
}
}
/// Cycles, hides or displays content controllers based on the existence of
/// the controllers: cycles if both the incoming and outgoing controllers
/// exist; hides if only the outgoing controller exists; or displays if only
/// the incoming controller exists.
public func cycle(from content: UIViewController?,
// swiftlint:disable:previous function_parameter_count
to newContent: UIViewController?,
duration: TimeInterval,
options: UIViewAnimationOptions,
newContentStartFrame: CGRect?,
contentEndFrame: CGRect?) {
if let content = content {
if let newContent = newContent {
cycle(from: content,
to: newContent,
duration: duration,
options: options,
newContentStartFrame: newContentStartFrame,
contentEndFrame: contentEndFrame)
} else {
hide(content: content)
}
} else {
if let newContent = newContent {
display(content: newContent)
}
}
}
/// Supports view controller transitions. Allows concatenating of multiple
/// animations and completion blocks.
public struct Transition {
public typealias Animations = () -> Void
public typealias Completion = (Bool) -> Void
public let controller: UIViewController
public var options: UIViewAnimationOptions = []
public var animations: Animations?
public var completion: Completion?
public init(controller: UIViewController, completion: Completion? = nil) {
self.controller = controller
self.completion = completion
}
/// Adds an animation block.
/// - parameter animations: Block to add to animations.
public mutating func addAnimations(_ animations: @escaping Animations) {
if let currentAnimations = self.animations {
self.animations = {
currentAnimations()
animations()
}
} else {
self.animations = animations
}
}
/// Adds a completion block.
/// - parameter completion: Block to add to chain of completion blocks.
public mutating func addCompletion(_ completion: @escaping Completion) {
if let currentCompletion = self.completion {
self.completion = { (finished) in
currentCompletion(finished)
completion(finished)
}
} else {
self.completion = completion
}
}
/// Displays the given content within this container.
///
/// Adds the other view controller's (content) view to this container's view
/// hierarchy. Automatically removes the content from any other container
/// before adding it to this container, invoking
/// `willMoveToParentViewController` on the content view controller.
///
/// - parameter content: Content view controller to display within this
/// container's content hierarchy.
public func display(content: UIViewController) {
controller.addChildViewController(content)
controller.view.addSubview(content.view)
content.didMove(toParentViewController: controller)
}
/// Hides the given content.
public func hide(content: UIViewController) {
content.willMove(toParentViewController: nil)
content.view.removeFromSuperview()
content.removeFromParentViewController()
}
/// Cycles from an existing content view controller to another new content
/// view controller. Handles the will- and did-move-to-parent messages for
/// both the incoming and outgoing view controllers. Removes the existing
/// content view controller from the parent view controller when animation
/// finishes.
///
/// - parameter from: Outgoing content view controller.
/// - parameter to: New incoming content view controller.
/// - parameter duration: Total duration of animations in seconds.
public func cycle(from: UIViewController, to: UIViewController, duration: TimeInterval) {
from.willMove(toParentViewController: nil)
controller.addChildViewController(to)
controller.transition(from: from, to: to, duration: duration, options: options, animations: animations) { (finished) in
from.removeFromParentViewController()
to.didMove(toParentViewController: self.controller)
self.completion?(finished)
}
}
/// Cycles, hides or displays content controllers based on the existence of
/// the controllers: cycles if both the incoming and outgoing controllers
/// exist; hides if only the outgoing controller exists; or displays if only
/// the incoming controller exists.
///
/// - parameter from: Outgoing content view controller, optional.
/// - parameter to: New incoming content view controller, optional.
/// - parameter duration: Total duration of animations in seconds. Only used
/// when cycling from and to content controllers.
public func cycle(from: UIViewController?, to: UIViewController?, duration: TimeInterval) {
if let from = from {
if let to = to {
cycle(from: from, to: to, duration: duration)
} else {
hide(content: from)
}
} else {
if let to = to {
display(content: to)
}
}
}
}
}