/
BrowserTrayAnimators.swift
328 lines (271 loc) · 15.6 KB
/
BrowserTrayAnimators.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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import UIKit
class TrayToBrowserAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if let bvc = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as? BrowserViewController,
let tabTray = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as? TabTrayController {
transitionFromTray(tabTray, toBrowser: bvc, usingContext: transitionContext)
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.4
}
}
private extension TrayToBrowserAnimator {
func transitionFromTray(tabTray: TabTrayController, toBrowser bvc: BrowserViewController, usingContext transitionContext: UIViewControllerContextTransitioning) {
guard let container = transitionContext.containerView() else { return }
guard let selectedTab = bvc.tabManager.selectedTab else { return }
// Bug 1205464 - Top Sites tiles blow up or shrink after rotating
// Force the BVC's frame to match the tab trays since for some reason on iOS 9 the UILayoutContainer in
// the UINavigationController doesn't rotate the presenting view controller
let os = NSProcessInfo().operatingSystemVersion
switch (os.majorVersion, os.minorVersion, os.patchVersion) {
case (9, _, _):
bvc.view.frame = UIWindow().frame
default:
break
}
let tabManager = bvc.tabManager
let displayedTabs = selectedTab.isPrivate ? tabManager.privateTabs : tabManager.normalTabs
guard let expandFromIndex = displayedTabs.indexOf(selectedTab) else { return }
// Hide browser components
bvc.toggleSnackBarVisibility(show: false)
toggleWebViewVisibility(show: false, usingTabManager: bvc.tabManager)
bvc.homePanelController?.view.hidden = true
bvc.webViewContainerBackdrop.hidden = true
// Take a snapshot of the collection view that we can scale/fade out. We don't need to wait for screen updates since it's already rendered on the screen
let tabCollectionViewSnapshot = tabTray.collectionView.snapshotViewAfterScreenUpdates(false)
tabTray.collectionView.alpha = 0
tabCollectionViewSnapshot.frame = tabTray.collectionView.frame
container.insertSubview(tabCollectionViewSnapshot, aboveSubview: tabTray.view)
// Create a fake cell to use for the upscaling animation
let startingFrame = calculateCollapsedCellFrameUsingCollectionView(tabTray.collectionView, atIndex: expandFromIndex)
let cell = createTransitionCellFromBrowser(bvc.tabManager.selectedTab, withFrame: startingFrame)
cell.backgroundHolder.layer.cornerRadius = 0
container.insertSubview(bvc.view, aboveSubview: tabCollectionViewSnapshot)
container.insertSubview(cell, aboveSubview: bvc.view)
// Flush any pending layout/animation code in preperation of the animation call
container.layoutIfNeeded()
let finalFrame = calculateExpandedCellFrameFromBVC(bvc)
bvc.footer.alpha = shouldDisplayFooterForBVC(bvc) ? 1 : 0
bvc.urlBar.isTransitioning = true
// Re-calculate the starting transforms for header/footer views in case we switch orientation
resetTransformsForViews([bvc.header, bvc.headerBackdrop, bvc.readerModeBar, bvc.footer, bvc.footerBackdrop])
transformHeaderFooterForBVC(bvc, toFrame: startingFrame, container: container)
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0, usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations:
{
// Scale up the cell and reset the transforms for the header/footers
cell.frame = finalFrame
container.layoutIfNeeded()
cell.title.transform = CGAffineTransformMakeTranslation(0, -cell.title.frame.height)
resetTransformsForViews([bvc.header, bvc.footer, bvc.readerModeBar, bvc.footerBackdrop, bvc.headerBackdrop])
bvc.urlBar.updateAlphaForSubviews(1)
tabCollectionViewSnapshot.transform = CGAffineTransformMakeScale(0.9, 0.9)
tabCollectionViewSnapshot.alpha = 0
// Push out the navigation bar buttons
let buttonOffset = tabTray.addTabButton.frame.width + TabTrayControllerUX.ToolbarButtonOffset
tabTray.addTabButton.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, buttonOffset , 0)
tabTray.settingsButton.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -buttonOffset , 0)
if #available(iOS 9, *) {
tabTray.togglePrivateMode.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, buttonOffset , 0)
}
}, completion: { finished in
// Remove any of the views we used for the animation
cell.removeFromSuperview()
tabCollectionViewSnapshot.removeFromSuperview()
bvc.footer.alpha = 1
bvc.startTrackingAccessibilityStatus()
bvc.toggleSnackBarVisibility(show: true)
toggleWebViewVisibility(show: true, usingTabManager: bvc.tabManager)
bvc.webViewContainerBackdrop.hidden = false
bvc.homePanelController?.view.hidden = false
bvc.urlBar.isTransitioning = false
transitionContext.completeTransition(true)
})
}
}
class BrowserToTrayAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if let bvc = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as? BrowserViewController,
let tabTray = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as? TabTrayController {
transitionFromBrowser(bvc, toTabTray: tabTray, usingContext: transitionContext)
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.4
}
}
private extension BrowserToTrayAnimator {
func transitionFromBrowser(bvc: BrowserViewController, toTabTray tabTray: TabTrayController, usingContext transitionContext: UIViewControllerContextTransitioning) {
guard let container = transitionContext.containerView() else { return }
guard let selectedTab = bvc.tabManager.selectedTab else { return }
let tabManager = bvc.tabManager
let displayedTabs = selectedTab.isPrivate ? tabManager.privateTabs : tabManager.normalTabs
guard let scrollToIndex = displayedTabs.indexOf(selectedTab) else { return }
// Insert tab tray below the browser and force a layout so the collection view can get it's frame right
container.insertSubview(tabTray.view, belowSubview: bvc.view)
// Force subview layout on the collection view so we can calculate the correct end frame for the animation
tabTray.view.layoutSubviews()
tabTray.collectionView.scrollToItemAtIndexPath(NSIndexPath(forItem: scrollToIndex, inSection: 0), atScrollPosition: .CenteredVertically, animated: false)
// Build a tab cell that we will use to animate the scaling of the browser to the tab
let expandedFrame = calculateExpandedCellFrameFromBVC(bvc)
let cell = createTransitionCellFromBrowser(bvc.tabManager.selectedTab, withFrame: expandedFrame)
cell.backgroundHolder.layer.cornerRadius = TabTrayControllerUX.CornerRadius
cell.innerStroke.hidden = true
// Take a snapshot of the collection view to perform the scaling/alpha effect
let tabCollectionViewSnapshot = tabTray.collectionView.snapshotViewAfterScreenUpdates(true)
tabCollectionViewSnapshot.frame = tabTray.collectionView.frame
tabCollectionViewSnapshot.transform = CGAffineTransformMakeScale(0.9, 0.9)
tabCollectionViewSnapshot.alpha = 0
tabTray.view.addSubview(tabCollectionViewSnapshot)
container.addSubview(cell)
cell.layoutIfNeeded()
cell.title.transform = CGAffineTransformMakeTranslation(0, -cell.title.frame.size.height)
// Hide views we don't want to show during the animation in the BVC
bvc.homePanelController?.view.hidden = true
bvc.toggleSnackBarVisibility(show: false)
toggleWebViewVisibility(show: false, usingTabManager: bvc.tabManager)
bvc.urlBar.isTransitioning = true
// Since we are hiding the collection view and the snapshot API takes the snapshot after the next screen update,
// the screenshot ends up being blank unless we set the collection view hidden after the screen update happens.
// To work around this, we dispatch the setting of collection view to hidden after the screen update is completed.
dispatch_async(dispatch_get_main_queue()) {
tabTray.collectionView.hidden = true
let finalFrame = calculateCollapsedCellFrameUsingCollectionView(tabTray.collectionView,
atIndex: scrollToIndex)
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0, usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations:
{
cell.frame = finalFrame
cell.title.transform = CGAffineTransformIdentity
cell.layoutIfNeeded()
transformHeaderFooterForBVC(bvc, toFrame: finalFrame, container: container)
bvc.urlBar.updateAlphaForSubviews(0)
bvc.footer.alpha = 0
tabCollectionViewSnapshot.alpha = 1
var viewsToReset: [UIView?] = [tabCollectionViewSnapshot, tabTray.addTabButton, tabTray.settingsButton]
if #available(iOS 9, *) {
viewsToReset.append(tabTray.togglePrivateMode)
}
resetTransformsForViews(viewsToReset)
}, completion: { finished in
// Remove any of the views we used for the animation
cell.removeFromSuperview()
tabCollectionViewSnapshot.removeFromSuperview()
tabTray.collectionView.hidden = false
bvc.toggleSnackBarVisibility(show: true)
toggleWebViewVisibility(show: true, usingTabManager: bvc.tabManager)
bvc.homePanelController?.view.hidden = false
bvc.stopTrackingAccessibilityStatus()
bvc.urlBar.isTransitioning = false
transitionContext.completeTransition(true)
})
}
}
}
private func transformHeaderFooterForBVC(bvc: BrowserViewController, toFrame finalFrame: CGRect, container: UIView) {
let footerForTransform = footerTransform(bvc.footer.frame, toFrame: finalFrame, container: container)
let headerForTransform = headerTransform(bvc.header.frame, toFrame: finalFrame, container: container)
bvc.footer.transform = footerForTransform
bvc.footerBackdrop.transform = footerForTransform
bvc.header.transform = headerForTransform
bvc.readerModeBar?.transform = headerForTransform
bvc.headerBackdrop.transform = headerForTransform
}
private func footerTransform(var frame: CGRect, toFrame finalFrame: CGRect, container: UIView) -> CGAffineTransform {
frame = container.convertRect(frame, toView: container)
let endY = CGRectGetMaxY(finalFrame) - (frame.size.height / 2)
let endX = CGRectGetMidX(finalFrame)
let translation = CGPoint(x: endX - CGRectGetMidX(frame), y: endY - CGRectGetMidY(frame))
let scaleX = finalFrame.width / frame.width
var transform = CGAffineTransformIdentity
transform = CGAffineTransformTranslate(transform, translation.x, translation.y)
transform = CGAffineTransformScale(transform, scaleX, scaleX)
return transform
}
private func headerTransform(var frame: CGRect, toFrame finalFrame: CGRect, container: UIView) -> CGAffineTransform {
frame = container.convertRect(frame, toView: container)
let endY = CGRectGetMinY(finalFrame) + (frame.size.height / 2)
let endX = CGRectGetMidX(finalFrame)
let translation = CGPoint(x: endX - CGRectGetMidX(frame), y: endY - CGRectGetMidY(frame))
let scaleX = finalFrame.width / frame.width
var transform = CGAffineTransformIdentity
transform = CGAffineTransformTranslate(transform, translation.x, translation.y)
transform = CGAffineTransformScale(transform, scaleX, scaleX)
return transform
}
//MARK: Private Helper Methods
private func calculateCollapsedCellFrameUsingCollectionView(collectionView: UICollectionView, atIndex index: Int) -> CGRect {
if let attr = collectionView.collectionViewLayout.layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: index, inSection: 0)) {
return collectionView.convertRect(attr.frame, toView: collectionView.superview)
} else {
return CGRectZero
}
}
private func calculateExpandedCellFrameFromBVC(bvc: BrowserViewController) -> CGRect {
var frame = bvc.webViewContainer.frame
// If we're navigating to a home panel and we were expecting to show the toolbar, add more height to end frame since
// there is no toolbar for home panels
if !bvc.shouldShowFooterForTraitCollection(bvc.traitCollection) {
return frame
} else if AboutUtils.isAboutURL(bvc.tabManager.selectedTab?.url) {
frame.size.height += UIConstants.ToolbarHeight
}
return frame
}
private func shouldDisplayFooterForBVC(bvc: BrowserViewController) -> Bool {
return bvc.shouldShowFooterForTraitCollection(bvc.traitCollection) && !AboutUtils.isAboutURL(bvc.tabManager.selectedTab?.url)
}
private func toggleWebViewVisibility(show show: Bool, usingTabManager tabManager: TabManager) {
for i in 0..<tabManager.count {
if let tab = tabManager[i] {
tab.webView?.hidden = !show
}
}
}
private func resetTransformsForViews(views: [UIView?]) {
for view in views {
// Reset back to origin
view?.transform = CGAffineTransformIdentity
}
}
private func transformToolbarsToFrame(toolbars: [UIView?], toRect endRect: CGRect) {
for toolbar in toolbars {
// Reset back to origin
toolbar?.transform = CGAffineTransformIdentity
// Transform from origin to where we want them to end up
if let toolbarFrame = toolbar?.frame {
toolbar?.transform = CGAffineTransformMakeRectToRect(toolbarFrame, toFrame: endRect)
}
}
}
private func createTransitionCellFromBrowser(browser: Browser?, withFrame frame: CGRect) -> TabCell {
let cell = TabCell(frame: frame)
cell.background.image = browser?.screenshot
cell.titleText.text = browser?.displayTitle
if let browser = browser where browser.isPrivate {
cell.style = .Dark
}
if let favIcon = browser?.displayFavicon {
cell.favicon.sd_setImageWithURL(NSURL(string: favIcon.url)!)
} else {
var defaultFavicon = UIImage(named: "defaultFavicon")
if browser?.isPrivate ?? false {
defaultFavicon = defaultFavicon?.imageWithRenderingMode(.AlwaysTemplate)
cell.favicon.image = defaultFavicon
cell.favicon.tintColor = (browser?.isPrivate ?? false) ? UIColor.whiteColor() : UIColor.darkGrayColor()
} else {
cell.favicon.image = defaultFavicon
}
}
return cell
}