From 41aa541bbf6ab08ffbb7c6961a79d46121096a82 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 28 Mar 2016 17:13:25 +0300 Subject: [PATCH] no message --- Display.xcodeproj/project.pbxproj | 96 +++- Display/ActionSheet.swift | 7 + Display/ActionSheetItemNode.swift | 8 + Display/CAAnimationUtils.swift | 29 +- Display/DefaultDisplayTheme.swift | 5 + Display/Display.h | 3 + Display/DisplayLinkDispatcher.swift | 34 ++ Display/DisplayTheme.swift | 9 + Display/FBAnimationPerformanceTracker.h | 136 ++++++ Display/FBAnimationPerformanceTracker.mm | 412 ++++++++++++++++++ Display/GenerateImage.swift | 258 +++++++++++ Display/ImageCache.swift | 6 +- Display/NSWeakReference.h | 9 + Display/NSWeakReference.m | 13 + Display/NavigationBackButtonNode.swift | 16 +- Display/NavigationBar.swift | 78 ++-- .../NavigationBarTransitionContainer.swift | 6 + Display/NavigationButtonNode.swift | 14 +- Display/NavigationController.swift | 267 ++++++++++-- Display/NavigationItemWrapper.swift | 2 +- Display/NavigationShadow@2x.png | Bin 0 -> 1116 bytes Display/NavigationTitleNode.swift | 2 +- Display/NavigationTransitionCoordinator.swift | 158 +++++++ Display/NavigationTransitionView.swift | 82 ---- Display/RuntimeUtils.h | 1 + Display/RuntimeUtils.m | 10 + Display/StatusBar.swift | 59 +++ Display/StatusBarManager.swift | 151 +++++++ Display/StatusBarProxyNode.swift | 333 ++++++++++++++ Display/StatusBarSurfaceProvider.swift | 4 + Display/StatusBarUtils.h | 9 + Display/StatusBarUtils.m | 32 ++ Display/TabBarController.swift | 2 +- Display/TabBarNode.swift | 8 +- Display/UIKitUtils.swift | 2 +- Display/UIViewController+Navigation.h | 4 + Display/UIViewController+Navigation.m | 125 ++++++ Display/ViewController.swift | 33 +- Display/Window.swift | 17 +- 39 files changed, 2230 insertions(+), 210 deletions(-) create mode 100644 Display/ActionSheet.swift create mode 100644 Display/ActionSheetItemNode.swift create mode 100644 Display/DefaultDisplayTheme.swift create mode 100644 Display/DisplayLinkDispatcher.swift create mode 100644 Display/DisplayTheme.swift create mode 100644 Display/FBAnimationPerformanceTracker.h create mode 100644 Display/FBAnimationPerformanceTracker.mm create mode 100644 Display/GenerateImage.swift create mode 100644 Display/NSWeakReference.h create mode 100644 Display/NSWeakReference.m create mode 100644 Display/NavigationBarTransitionContainer.swift create mode 100644 Display/NavigationShadow@2x.png create mode 100644 Display/NavigationTransitionCoordinator.swift delete mode 100644 Display/NavigationTransitionView.swift create mode 100644 Display/StatusBar.swift create mode 100644 Display/StatusBarManager.swift create mode 100644 Display/StatusBarProxyNode.swift create mode 100644 Display/StatusBarSurfaceProvider.swift create mode 100644 Display/StatusBarUtils.h create mode 100644 Display/StatusBarUtils.m diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index ff3255d01f..3bab6c708f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,7 +7,19 @@ objects = { /* Begin PBXBuildFile section */ + D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; + D0078A6D1C92B3B900DF6D92 /* ActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */; }; + D0078A6F1C92B40300DF6D92 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */; }; + D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */; }; + D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; + D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */; }; + D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E7DF61C96C5F200C07816 /* NSWeakReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DF71C96C5F200C07816 /* NSWeakReference.m */; }; + D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */; }; + D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */; }; + D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; @@ -33,7 +45,7 @@ D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC3071B69575900E235A3 /* NSBag.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3051B69575900E235A3 /* NSBag.m */; }; D05CC3081B69575900E235A3 /* NSBag.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3061B69575900E235A3 /* NSBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */; }; + D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */; }; D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30A1B695A9500E235A3 /* NavigationBar.swift */; }; D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */; }; D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */; }; @@ -53,6 +65,12 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; + D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */; }; + D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; + D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; + D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; @@ -70,7 +88,19 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; + D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheet.swift; sourceTree = ""; }; + D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayTheme.swift; sourceTree = ""; }; + D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultDisplayTheme.swift; sourceTree = ""; }; + D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; + D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionContainer.swift; sourceTree = ""; }; + D03E7DF61C96C5F200C07816 /* NSWeakReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSWeakReference.h; sourceTree = ""; }; + D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = ""; }; + D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = ""; }; + D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; + D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarSurfaceProvider.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -99,7 +129,7 @@ D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationCenterUtils.h; sourceTree = ""; }; D05CC3051B69575900E235A3 /* NSBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBag.m; sourceTree = ""; }; D05CC3061B69575900E235A3 /* NSBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBag.h; sourceTree = ""; }; - D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionView.swift; sourceTree = ""; }; + D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionCoordinator.swift; sourceTree = ""; }; D05CC30A1B695A9500E235A3 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = ""; }; D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemWrapper.swift; sourceTree = ""; }; D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemTransitionState.swift; sourceTree = ""; }; @@ -119,6 +149,12 @@ D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; + D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarUtils.h; sourceTree = ""; }; + D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarUtils.m; sourceTree = ""; }; + D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; + D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; + D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; + D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; @@ -146,6 +182,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D0078A6B1C92B3A600DF6D92 /* Action Sheet */ = { + isa = PBXGroup; + children = ( + D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */, + D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */, + ); + name = "Action Sheet"; + sourceTree = ""; + }; D02BDAEC1B6A7053008AFAD2 /* Nodes */ = { isa = PBXGroup; children = ( @@ -153,6 +198,15 @@ name = Nodes; sourceTree = ""; }; + D03BCCE91C72AE4B0097A291 /* Theme */ = { + isa = PBXGroup; + children = ( + D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */, + D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */, + ); + name = Theme; + sourceTree = ""; + }; D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( @@ -175,10 +229,12 @@ D05CC2651B69316F00E235A3 /* Display */ = { isa = PBXGroup; children = ( + D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, + D0078A6B1C92B3A600DF6D92 /* Action Sheet */, D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, D0E49C861B83A1680099E553 /* Image Cache */, @@ -210,6 +266,7 @@ D05CC2E11B69534100E235A3 /* Supporting Files */ = { isa = PBXGroup; children = ( + D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */, D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */, D05CC2661B69316F00E235A3 /* Display.h */, D05CC2681B69316F00E235A3 /* Info.plist */, @@ -243,9 +300,17 @@ D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */, D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */, D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */, + D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */, + D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */, D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */, D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */, D06EE8441B7140FF00837186 /* Font.swift */, + D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */, + D03E7DF61C96C5F200C07816 /* NSWeakReference.h */, + D03E7DF71C96C5F200C07816 /* NSWeakReference.m */, + D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */, + D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */, + D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */, ); name = Utils; sourceTree = ""; @@ -253,7 +318,8 @@ D05CC3211B695AA600E235A3 /* Navigation */ = { isa = PBXGroup; children = ( - D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */, + D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */, + D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */, D05CC30A1B695A9500E235A3 /* NavigationBar.swift */, D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */, D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, @@ -279,6 +345,10 @@ isa = PBXGroup; children = ( D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */, + D0078A671C92B21400DF6D92 /* StatusBar.swift */, + D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */, + D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */, + D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */, ); name = "Status Bar"; sourceTree = ""; @@ -311,13 +381,16 @@ D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */, D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */, D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */, + D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */, D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */, + D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */, D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */, D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */, D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */, D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */, D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, D05CC3081B69575900E235A3 /* NSBag.h in Headers */, + D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */, D05CC2671B69316F00E235A3 /* Display.h in Headers */, D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */, ); @@ -403,6 +476,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */, D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -422,8 +496,11 @@ buildActionMask = 2147483647; files = ( D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, + D0078A6F1C92B40300DF6D92 /* ActionSheetItemNode.swift in Sources */, + D0078A6D1C92B3B900DF6D92 /* ActionSheet.swift in Sources */, D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */, + D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, @@ -433,12 +510,17 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */, D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, + D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, + D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, + D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, + D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, @@ -448,12 +530,18 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, + D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */, + D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */, + D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, + D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, + D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, + D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, - D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */, + D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Display/ActionSheet.swift b/Display/ActionSheet.swift new file mode 100644 index 0000000000..5feab284f9 --- /dev/null +++ b/Display/ActionSheet.swift @@ -0,0 +1,7 @@ +import Foundation + +public class ActionSheet { + public init() { + + } +} diff --git a/Display/ActionSheetItemNode.swift b/Display/ActionSheetItemNode.swift new file mode 100644 index 0000000000..bae40f9bf6 --- /dev/null +++ b/Display/ActionSheetItemNode.swift @@ -0,0 +1,8 @@ +import Foundation +import AsyncDisplayKit + +public class ActionSheetItemNode: ASDisplayNode { + override public init() { + super.init() + } +} \ No newline at end of file diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 53c87297af..f8aabe340d 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -61,15 +61,42 @@ public extension CALayer { //self.setValue(to, forKey: keyPath) } + public func animateAdditive(from from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.removedOnCompletion = removeOnCompletion + animation.fillMode = kCAFillModeForwards + animation.speed = speed + animation.additive = true + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + self.addAnimation(animation, forKey: key) + } + public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } + public func animateScale(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) + } + internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { - self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) + self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } } diff --git a/Display/DefaultDisplayTheme.swift b/Display/DefaultDisplayTheme.swift new file mode 100644 index 0000000000..74f95ebf48 --- /dev/null +++ b/Display/DefaultDisplayTheme.swift @@ -0,0 +1,5 @@ +import Foundation + +func defaultDisplayTheme() -> DisplayTheme { + return DisplayTheme(tintColor: UIColor.blueColor()) +} diff --git a/Display/Display.h b/Display/Display.h index 8cb75256da..72849d0699 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -27,4 +27,7 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import +#import #import +#import +#import diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift new file mode 100644 index 0000000000..5210d7fc9e --- /dev/null +++ b/Display/DisplayLinkDispatcher.swift @@ -0,0 +1,34 @@ +import Foundation + +public class DisplayLinkDispatcher: NSObject { + private var displayLink: CADisplayLink! + private var blocksToDispatch: [Void -> Void] = [] + private let limit: Int + + public init(limit: Int = 0) { + self.limit = limit + + super.init() + + self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) + self.displayLink.paused = true + self.displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) + } + + public func dispatch(f: Void -> Void) { + self.blocksToDispatch.append(f) + self.displayLink.paused = false + } + + @objc func run() { + for _ in 0 ..< (self.limit == 0 ? 1000 : self.limit) { + if self.blocksToDispatch.count == 0 { + self.displayLink.paused = true + break + } else { + let f = self.blocksToDispatch.removeFirst() + f() + } + } + } +} \ No newline at end of file diff --git a/Display/DisplayTheme.swift b/Display/DisplayTheme.swift new file mode 100644 index 0000000000..6a96470066 --- /dev/null +++ b/Display/DisplayTheme.swift @@ -0,0 +1,9 @@ +import Foundation + +public class DisplayTheme { + var tintColor: UIColor + + public init(tintColor: UIColor) { + self.tintColor = tintColor + } +} diff --git a/Display/FBAnimationPerformanceTracker.h b/Display/FBAnimationPerformanceTracker.h new file mode 100644 index 0000000000..f9cad92ae9 --- /dev/null +++ b/Display/FBAnimationPerformanceTracker.h @@ -0,0 +1,136 @@ +#import + +/* + * This is an example provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK 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. + * + * + * FBAnimationPerformanceTracker + * ----------------------------------------------------------------------- + * + * This class provides animation performance tracking functionality. It basically + * measures the app's frame rate during an operation, and reports this information. + * + * 1) In Foo's designated initializer, construct a tracker object + * + * 2) Add calls to -start and -stop in appropriate places, e.g. for a ScrollView + * + * - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + * [_apTracker start]; + * } + * + * - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView + * { + * if (!scrollView.dragging) { + * [_apTracker stop]; + * } + * } + * + * - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + * if (!decelerate) { + * [_apTracker stop]; + * } + * } + * + * Notes + * ----- + * [] The tracker operates by creating a CADisplayLink object to measure the frame rate of the display + * during start/stop interval. + * + * [] Calls to -stop that were not preceded by a matching call to -start have no effect. + * + * [] 2 calls to -start in a row will trash the data accumulated so far and not log anything. + * + * + * Configuration object for the core tracker + * + * =============================================================================== + * I highly recommend for you to use the standard configuration provided + * These are essentially here so that the computation of the metric is transparent + * and you can feel confident in what the numbers mean. + * =============================================================================== + */ +struct FBAnimationPerformanceTrackerConfig +{ + // Number of frame drop that defines a "small" drop event. By default, 1. + NSInteger smallDropEventFrameNumber; + // Number of frame drop that defines a "large" drop event. By default, 4. + NSInteger largeDropEventFrameNumber; + // Number of maximum frame drops to which the drop will be trimmed down to. Currently 15. + NSInteger maxFrameDropAccount; + + // If YES, will report stack traces + BOOL reportStackTraces; +}; +typedef struct FBAnimationPerformanceTrackerConfig FBAnimationPerformanceTrackerConfig; + + +@protocol FBAnimationPerformanceTrackerDelegate + +/** + * Core Metric + * + * You are responsible for the aggregation of these metrics (it being on the client or the server). I recommend to implement both + * to limit the payload you are sending to the server. + * + * The final recommended metric being: - SUM(duration) / SUM(smallDropEvent) aka the number of seconds between one frame drop or more + * - SUM(duration) / SUM(largeDropEvent) aka the number of seconds between four frame drops or more + * + * The first metric will tell you how smooth is your scroll view. + * The second metric will tell you how clowny your scroll view can get. + * + * Every time stop is called, this event will fire reporting the performance. + * + * NOTE on this metric: + * - It has been tested at scale on many Facebook apps. + * - It follows the curves of devices. + * - You will need about 100K calls for the number to converge. + * - It is perfectly correlated to X = Percentage of time spent at 60fps. Number of seconds between one frame drop = 1 / ( 1 - Time spent at 60 fps) + * - We report fraction of drops. 7 frame drop = 1.75 of a large frame drop if a large drop is 4 frame drop. + * This is to preserve the correlation mentionned above. + */ +- (void)reportDurationInMS:(NSInteger)duration smallDropEvent:(double)smallDropEvent largeDropEvent:(double)largeDropEvent; + +/** + * Stack traces + * + * Dark magic of the animation tracker. In case of a frame drop, this will return a stack trace. + * This will NOT be reported on the main-thread, but off-main thread to save a few CPU cycles. + * + * The slide is constant value that needs to be reported with the stack for processing. + * This currently only allows for symbolication of your own image. + * + * Future work includes symbolicating all modules. I personnaly find it usually + * good enough to know the name of the module. + * + * The stack will have the following format: + * Foundation:0x123|MyApp:0x234|MyApp:0x345| + * + * The slide will have the following format: + * 0x456 + */ +- (void)reportStackTrace:(NSString *)stack withSlide:(NSString *)slide; + +@end + +@interface FBAnimationPerformanceTracker : NSObject + +- (instancetype)initWithConfig:(FBAnimationPerformanceTrackerConfig)config; + ++ (FBAnimationPerformanceTrackerConfig)standardConfig; + +@property (weak, nonatomic, readwrite) id delegate; + +- (void)start; +- (void)stop; + +@end diff --git a/Display/FBAnimationPerformanceTracker.mm b/Display/FBAnimationPerformanceTracker.mm new file mode 100644 index 0000000000..7257c338b8 --- /dev/null +++ b/Display/FBAnimationPerformanceTracker.mm @@ -0,0 +1,412 @@ +// +// FBAnimationPerformanceTracker.m +// Display +// +// Created by Peter on 3/16/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +#import "FBAnimationPerformanceTracker.h" + +#import +#import +#import + +#import + +#import + +#import "execinfo.h" + +#include + +static BOOL _signalSetup; +static pthread_t _mainThread; +static NSThread *_trackerThread; + +static std::map> _imageNames; + +#ifdef __LP64__ +typedef mach_header_64 fb_mach_header; +typedef segment_command_64 fb_mach_segment_command; +#define LC_SEGMENT_ARCH LC_SEGMENT_64 +#else +typedef mach_header fb_mach_header; +typedef segment_command fb_mach_segment_command; +#define LC_SEGMENT_ARCH LC_SEGMENT +#endif + +static volatile BOOL _scrolling; +pthread_mutex_t _scrollingMutex; +pthread_cond_t _scrollingCondVariable; +dispatch_queue_t _symbolicationQueue; + +// We record at most 16 frames since I cap the number of frames dropped measured at 15. +// Past 15, something went very wrong (massive contention, priority inversion, rpc call going wrong...) . +// It will only pollute the data to get more. +static const int callstack_max_number = 16; + +static int callstack_i; +static bool callstack_dirty; +static int callstack_size[callstack_max_number]; +static void *callstacks[callstack_max_number][128]; +uint64_t callstack_time_capture; + +static void _callstack_signal_handler(int signr, siginfo_t *info, void *secret) +{ + // This is run on the main thread every 16 ms or so during scroll. + + // Signals are run one by one so there is no risk of concurrency of a signal + // by the same signal. + + // The backtrace call is technically signal-safe on Unix-based system + // See: http://www.unix.com/man-page/all/3c/walkcontext/ + + // WARNING: this is signal handler, no memory allocation is safe. + // Essentially nothing is safe unless specified it is. + callstack_size[callstack_i] = backtrace(callstacks[callstack_i], 128); + callstack_i = (callstack_i + 1) & (callstack_max_number - 1); // & is a cheap modulo (only works for power of 2) + callstack_dirty = true; +} + +@interface FBCallstack : NSObject +@property (nonatomic, readonly, assign) int size; +@property (nonatomic, readonly, assign) void **callstack; +- (instancetype)initWithSize:(int)size callstack:(void *)callstack; +@end + +@implementation FBCallstack +- (instancetype)initWithSize:(int)size callstack:(void *)callstack +{ + if (self = [super init]) { + _size = size; + _callstack = (void **)malloc(size * sizeof(void *)); + memcpy(_callstack, callstack, size * sizeof(void *)); + } + return self; +} + +- (void)dealloc +{ + free(_callstack); +} +@end + +@implementation FBAnimationPerformanceTracker +{ + FBAnimationPerformanceTrackerConfig _config; + + BOOL _tracking; + BOOL _firstUpdate; + NSTimeInterval _previousFrameTimestamp; + CADisplayLink *_displayLink; + BOOL _prepared; + + // numbers used to track the performance metrics + double _durationTotal; + double _maxFrameTime; + double _smallDrops; + double _largeDrops; +} + +- (instancetype)initWithConfig:(FBAnimationPerformanceTrackerConfig)config +{ + if (self = [super init]) { + // Stack trace logging is not working well in debug mode + // We don't want the data anyway. So let's bail. +#if defined(DEBUG) + config.reportStackTraces = NO; +#endif + _config = config; + if (config.reportStackTraces) { + [self _setupSignal]; + } + } + return self; +} + ++ (FBAnimationPerformanceTrackerConfig)standardConfig +{ + FBAnimationPerformanceTrackerConfig config = { + .smallDropEventFrameNumber = 1, + .largeDropEventFrameNumber = 4, + .maxFrameDropAccount = 15, + .reportStackTraces = NO + }; + return config; +} + ++ (void)_trackerLoop +{ + while (true) { + // If you are confused by this part, + // Check out https://computing.llnl.gov/tutorials/pthreads/#ConditionVariables + + // Lock the mutex + pthread_mutex_lock(&_scrollingMutex); + while (!_scrolling) { + // Unlock the mutex and sleep until the conditional variable is signaled + pthread_cond_wait(&_scrollingCondVariable, &_scrollingMutex); + // The conditional variable was signaled, but we need to check _scrolling + // As nothing guarantees that it is still true + } + // _scrolling is true, go ahead and capture traces for a while. + pthread_mutex_unlock(&_scrollingMutex); + + // We are scrolling, yay, capture traces + while (_scrolling) { + usleep(16000); + + // Here I use SIGPROF which is a signal supposed to be used for profiling + // I haven't stumbled upon any collision so far. + // There is no guarantee that it won't impact the system in unpredicted ways. + // Use wisely. + + pthread_kill(_mainThread, SIGPROF); + } + } +} + +- (void)_setupSignal +{ + if (!_signalSetup) { + // The signal hook should be setup once and only once + _signalSetup = YES; + + // I actually don't know if the main thread can die. If it does, well, + // this is not going to work. + // UPDATE 4/2015: on iOS8, it looks like the main-thread never dies, and this pointer is correct + _mainThread = pthread_self(); + + callstack_i = 0; + + // Setup the signal + struct sigaction sa; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = _callstack_signal_handler; + sigaction(SIGPROF, &sa, NULL); + + pthread_mutex_init(&_scrollingMutex, NULL); + pthread_cond_init (&_scrollingCondVariable, NULL); + + // Setup the signal firing loop + _trackerThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(_trackerLoop) object:nil]; + // We wanna be higher priority than the main thread + // On iOS8 : this will roughly stick us at priority 61, while the main thread oscillates between 20 and 47 + _trackerThread.threadPriority = 1.0; + [_trackerThread start]; + + _symbolicationQueue = dispatch_queue_create("com.facebook.symbolication", DISPATCH_QUEUE_SERIAL); + dispatch_async(_symbolicationQueue, ^(void) {[self _setupSymbolication];}); + } +} + +- (void)_setupSymbolication +{ + // This extract the starting slide of every module in the app + // This is used to know which module an instruction pointer belongs to. + + // These operations is NOT thread-safe according to Apple docs + // Do not call this multiple times + int images = _dyld_image_count(); + + for (int i = 0; i < images; i ++) { + intptr_t imageSlide = _dyld_get_image_vmaddr_slide(i); + + // Here we extract the module name from the full path + // Typically it looks something like: /path/to/lib/UIKit + // And I just extract UIKit + NSString *fullName = [NSString stringWithUTF8String:_dyld_get_image_name(i)]; + NSRange range = [fullName rangeOfString:@"/" options:NSBackwardsSearch]; + NSUInteger startP = (range.location != NSNotFound) ? range.location + 1 : 0; + NSString *imageName = [fullName substringFromIndex:startP]; + + // This is parsing the mach header in order to extract the slide. + // See https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/index.html + // For the structure of mach headers + fb_mach_header *header = (fb_mach_header*)_dyld_get_image_header(i); + if (!header) { + continue; + } + + const struct load_command *cmd = + reinterpret_cast(header + 1); + + for (unsigned int c = 0; cmd && (c < header->ncmds); c++) { + if (cmd->cmd == LC_SEGMENT_ARCH) { + const fb_mach_segment_command *seg = + reinterpret_cast(cmd); + + if (!strcmp(seg->segname, "__TEXT")) { + _imageNames[(void *)(seg->vmaddr + imageSlide)] = imageName; + break; + } + } + cmd = reinterpret_cast((char *)cmd + cmd->cmdsize); + } + } +} + +- (void)dealloc +{ + if (_prepared) { + [self _tearDownCADisplayLink]; + } +} + +#pragma mark - Tracking + +- (void)start +{ + if (!_tracking) { + if ([self prepare]) { + _displayLink.paused = NO; + _tracking = YES; + [self _reset]; + + if (_config.reportStackTraces) { + pthread_mutex_lock(&_scrollingMutex); + _scrolling = YES; + // Signal the tracker thread to start firing the signals + pthread_cond_signal(&_scrollingCondVariable); + pthread_mutex_unlock(&_scrollingMutex); + } + } + } +} + +- (void)stop +{ + if (_tracking) { + _tracking = NO; + _displayLink.paused = YES; + if (_durationTotal > 0) { + [_delegate reportDurationInMS:round(1000.0 * _durationTotal) smallDropEvent:_smallDrops largeDropEvent:_largeDrops]; + if (_config.reportStackTraces) { + pthread_mutex_lock(&_scrollingMutex); + _scrolling = NO; + pthread_mutex_unlock(&_scrollingMutex); + } + } + } +} + +- (BOOL)prepare +{ + if (_prepared) { + return YES; + } + + [self _setUpCADisplayLink]; + _prepared = YES; + + return YES; +} + +- (void)_setUpCADisplayLink +{ + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update)]; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + _displayLink.paused = YES; +} + +- (void)_tearDownCADisplayLink +{ + [_displayLink invalidate]; + _displayLink = nil; +} + +- (void)_reset +{ + _firstUpdate = YES; + _previousFrameTimestamp = 0.0; + _durationTotal = 0; + _maxFrameTime = 0; + _largeDrops = 0; + _smallDrops = 0; +} + +- (void)_addFrameTime:(NSTimeInterval)actualFrameTime singleFrameTime:(NSTimeInterval)singleFrameTime +{ + _maxFrameTime = MAX(actualFrameTime, _maxFrameTime); + + NSInteger frameDropped = round(actualFrameTime / singleFrameTime) - 1; + frameDropped = MAX(frameDropped, 0); + // This is to reduce noise. Massive frame drops will just add noise to your data. + frameDropped = MIN(_config.maxFrameDropAccount, frameDropped); + + _durationTotal += (frameDropped + 1) * singleFrameTime; + // We account 2 frame drops as 2 small events. This way the metric correlates perfectly with Time at X fps. + _smallDrops += (frameDropped >= _config.smallDropEventFrameNumber) ? ((double) frameDropped) / (double)_config.smallDropEventFrameNumber : 0.0; + _largeDrops += (frameDropped >= _config.largeDropEventFrameNumber) ? ((double) frameDropped) / (double)_config.largeDropEventFrameNumber : 0.0; + + if (frameDropped >= 1) { + if (_config.reportStackTraces) { + callstack_dirty = false; + for (int ci = 0; ci <= frameDropped ; ci ++) { + // This is computing the previous indexes + // callstack - 1 - ci takes us back ci frames + // I want a positive number so I add callstack_max_number + // And then just modulo it, with & (callstack_max_number - 1) + int callstackPreviousIndex = ((callstack_i - 1 - ci) + callstack_max_number) & (callstack_max_number - 1); + FBCallstack *callstackCopy = [[FBCallstack alloc] initWithSize:callstack_size[callstackPreviousIndex] callstack:callstacks[callstackPreviousIndex]]; + // Check that in between the beginning and the end of the copy the signal did not fire + if (!callstack_dirty) { + // The copy has been made. We are now fine, let's punt the rest off main-thread. + __weak FBAnimationPerformanceTracker *weakSelf = self; + dispatch_async(_symbolicationQueue, ^(void) { + [weakSelf _reportStackTrace:callstackCopy]; + }); + } + } + } + } +} + +- (void)_update +{ + if (!_tracking) { + return; + } + + if (_firstUpdate) { + _firstUpdate = NO; + _previousFrameTimestamp = _displayLink.timestamp; + return; + } + + NSTimeInterval currentTimestamp = _displayLink.timestamp; + NSTimeInterval frameTime = currentTimestamp - _previousFrameTimestamp; + [self _addFrameTime:frameTime singleFrameTime:_displayLink.duration]; + _previousFrameTimestamp = currentTimestamp; +} + +- (void)_reportStackTrace:(FBCallstack *)callstack +{ + static NSString *slide; + static dispatch_once_t slide_predicate; + + dispatch_once(&slide_predicate, ^{ + slide = [NSString stringWithFormat:@"%p", (void *)_dyld_get_image_header(0)]; + }); + + @autoreleasepool { + NSMutableString *stack = [NSMutableString string]; + + for (int j = 2; j < callstack.size; j ++) { + void *instructionPointer = callstack.callstack[j]; + auto it = _imageNames.lower_bound(instructionPointer); + + NSString *imageName = (it != _imageNames.end()) ? it->second : @"???"; + + [stack appendString:imageName]; + [stack appendString:@":"]; + [stack appendString:[NSString stringWithFormat:@"%p", instructionPointer]]; + [stack appendString:@"|"]; + } + + [_delegate reportStackTrace:stack withSlide:slide]; + } +} +@end diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift new file mode 100644 index 0000000000..ff19c6a151 --- /dev/null +++ b/Display/GenerateImage.swift @@ -0,0 +1,258 @@ +import Foundation +import UIKit + +let deviceColorSpace = CGColorSpaceCreateDeviceRGB() +let deviceScale = UIScreen.mainScreen().scale + +public func generateImage(size: CGSize, generator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { + let scale = deviceScale + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = UnsafeMutablePointer(malloc(length)) + let provider: CGDataProvider? = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + free(bytes) + }) + + generator(scaledSize, bytes) + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + guard let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) + else { + return nil + } + + return UIImage(CGImage: image, scale: scale, orientation: .Up) +} + +public func generateImage(size: CGSize, generator: (CGSize, CGContextRef) -> Void) -> UIImage? { + let scale = deviceScale + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = UnsafeMutablePointer(malloc(length)) + let provider: CGDataProvider? = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + free(bytes) + }) + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + + guard let context = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, bitmapInfo.rawValue) + else { + return nil + } + + CGContextScaleCTM(context, scale, scale) + + generator(size, context) + + guard let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) + else { + return nil + } + + return UIImage(CGImage: image, scale: scale, orientation: .Up) +} + +public enum DrawingContextBltMode { + case Alpha +} + +public class DrawingContext { + public let size: CGSize + public let scale: CGFloat + private let scaledSize: CGSize + public let bytesPerRow: Int + private let bitmapInfo: CGBitmapInfo + public let length: Int + public let bytes: UnsafeMutablePointer + let provider: CGDataProvider? + + private var _context: CGContext? + + public func withContext(@noescape f: (CGContext) -> ()) { + if self._context == nil { + let c = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, self.bitmapInfo.rawValue) + CGContextScaleCTM(c, scale, scale) + self._context = c + } + + if let _context = self._context { + CGContextTranslateCTM(_context, self.size.width / 2.0, self.size.height / 2.0) + CGContextScaleCTM(_context, 1.0, -1.0) + CGContextTranslateCTM(_context, -self.size.width / 2.0, -self.size.height / 2.0) + + f(_context) + + CGContextTranslateCTM(_context, self.size.width / 2.0, self.size.height / 2.0) + CGContextScaleCTM(_context, 1.0, -1.0) + CGContextTranslateCTM(_context, -self.size.width / 2.0, -self.size.height / 2.0) + } + } + + public func withFlippedContext(@noescape f: (CGContext) -> ()) { + if self._context == nil { + let c = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, self.bitmapInfo.rawValue) + CGContextScaleCTM(c, scale, scale) + self._context = c + } + + if let _context = self._context { + f(_context) + } + } + + public init(size: CGSize, clear: Bool = false) { + self.size = size + self.scale = deviceScale + self.scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + + self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + self.length = bytesPerRow * Int(scaledSize.height) + + self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + + self.bytes = UnsafeMutablePointer(malloc(length)) + if clear { + memset(self.bytes, 0, self.length) + } + self.provider = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + free(bytes) + }) + } + + public func generateImage() -> UIImage? { + if let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) { + return UIImage(CGImage: image, scale: scale, orientation: .Up) + } else { + return nil + } + } + + public func blt(other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { + if abs(other.scale - self.scale) < CGFloat(FLT_EPSILON) { + let srcX = 0 + var srcY = 0 + let dstX = Int(at.x * self.scale) + var dstY = Int(at.y * self.scale) + + let width = min(Int(self.size.width * self.scale) - dstX, Int(other.size.width * self.scale)) + let height = min(Int(self.size.height * self.scale) - dstY, Int(other.size.height * self.scale)) + + let maxDstX = dstX + width + let maxDstY = dstY + height + + switch mode { + case .Alpha: + while dstY < maxDstY { + let srcLine = UnsafeMutablePointer(other.bytes + srcY * other.bytesPerRow) + let dstLine = UnsafeMutablePointer(self.bytes + dstY * self.bytesPerRow) + + var dx = dstX + var sx = srcX + while dx < maxDstX { + let srcPixel = srcLine + sx + let dstPixel = dstLine + dx + + let baseColor = dstPixel.memory + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + let alpha = srcPixel.memory >> 24 + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + dstPixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + dx += 1 + sx += 1 + } + + dstY += 1 + srcY += 1 + } + } + } + } +} + +public enum ParsingError: ErrorType { + case Generic +} + +public func readCGFloat(inout index: UnsafePointer, end: UnsafePointer, separator: UInt8) throws -> CGFloat { + let begin = index + var seenPoint = false + while index <= end { + let c = index.memory + index = index.successor() + + if c == 46 { // . + if seenPoint { + throw ParsingError.Generic + } else { + seenPoint = true + } + } else if c == separator { + break + } else if c < 48 || c > 57 { + throw ParsingError.Generic + } + } + + if index == begin { + throw ParsingError.Generic + } + + if let value = NSString(bytes: UnsafePointer(begin), length: index - begin, encoding: NSUTF8StringEncoding)?.floatValue { + return CGFloat(value) + } else { + throw ParsingError.Generic + } +} + +public func drawSvgPath(context: CGContextRef, path: StaticString) throws { + var index: UnsafePointer = path.utf8Start + let end = path.utf8Start.advancedBy(path.byteSize) + while index < end { + let c = index.memory + index = index.successor() + + if c == 77 { // M + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + + //print("Move to \(x), \(y)") + CGContextMoveToPoint(context, x, y) + } else if c == 76 { // L + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + + //print("Line to \(x), \(y)") + CGContextAddLineToPoint(context, x, y) + } else if c == 67 { // C + let x1 = try readCGFloat(&index, end: end, separator: 44) + let y1 = try readCGFloat(&index, end: end, separator: 32) + let x2 = try readCGFloat(&index, end: end, separator: 44) + let y2 = try readCGFloat(&index, end: end, separator: 32) + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + CGContextAddCurveToPoint(context, x1, y1, x2, y2, x, y) + + //print("Line to \(x), \(y)") + + } else if c == 90 { // Z + if index != end && index.memory != 32 { + throw ParsingError.Generic + } + + //CGContextClosePath(context) + CGContextFillPath(context) + //CGContextBeginPath(context) + //print("Close") + } + } +} diff --git a/Display/ImageCache.swift b/Display/ImageCache.swift index f78e841305..de4913e500 100644 --- a/Display/ImageCache.swift +++ b/Display/ImageCache.swift @@ -110,7 +110,7 @@ public final class ImageCache { pthread_mutex_lock(&self.mutex); if let residentImage = self.residentImages[key] { image = residentImage.image - self.nextAccessIndex++ + self.nextAccessIndex += 1 residentImage.accessIndex = self.nextAccessIndex } else { if let imageData = self.imageDatas[key] { @@ -141,14 +141,14 @@ public final class ImageCache { let currentImageSize = Int(currentImage.image.size.width * currentImage.image.size.height * currentImage.image.scale) * 4 removedSize += currentImageSize self.residentImages.removeValueForKey(currentImage.key) - i-- + i -= 1 } self.residentImagesSize = max(0, self.residentImagesSize - removedSize) } self.residentImagesSize += imageSize - self.nextAccessIndex++ + self.nextAccessIndex += 1 self.residentImages[key] = ImageCacheResidentImage(key: key, image: image, accessIndex: self.nextAccessIndex) } } diff --git a/Display/NSWeakReference.h b/Display/NSWeakReference.h new file mode 100644 index 0000000000..8c762f1256 --- /dev/null +++ b/Display/NSWeakReference.h @@ -0,0 +1,9 @@ +#import + +@interface NSWeakReference : NSObject + +@property (nonatomic, weak) id value; + +- (instancetype)initWithValue:(id)value; + +@end diff --git a/Display/NSWeakReference.m b/Display/NSWeakReference.m new file mode 100644 index 0000000000..f9b714f3d7 --- /dev/null +++ b/Display/NSWeakReference.m @@ -0,0 +1,13 @@ +#import "NSWeakReference.h" + +@implementation NSWeakReference + +- (instancetype)initWithValue:(id)value { + self = [super init]; + if (self != nil) { + self.value = value; + } + return self; +} + +@end diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 6f483e09e1..16b8d70064 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -50,7 +50,7 @@ public class NavigationBackButtonNode: ASControlNode { self.label.displaysAsynchronously = false self.addSubnode(self.arrow) - let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil) + let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil)?.precomposed() self.arrow.contents = arrowImage?.CGImage self.arrow.frame = CGRect(origin: CGPoint(), size: arrowImage?.size ?? CGSize()) @@ -92,34 +92,34 @@ public class NavigationBackButtonNode: ASControlNode { return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) } - public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { super.touchesBegan(touches, withEvent: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } - public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { super.touchesMoved(touches, withEvent: event) - self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } - public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { super.touchesEnded(touches, withEvent: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) - if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } - public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { super.touchesCancelled(touches, withEvent: event) - self.touchCount = max(0, self.touchCount - touches.count) + self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index faf10e441d..4609490f9f 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1,23 +1,41 @@ import UIKit import AsyncDisplayKit -private enum ItemAnimation { - case None - case Push - case Pop -} - public class NavigationBar: ASDisplayNode { - private var topItem: UINavigationItem? - private var topItemWrapper: NavigationItemWrapper? + var item: UINavigationItem? { + didSet { + if let item = self.item { + self.itemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: self.previousItem) + self.itemWrapper?.backPressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + } else { + self.itemWrapper = nil + } + } + } - private var tempItem: UINavigationItem? - private var tempItemWrapper: NavigationItemWrapper? + var previousItem: UINavigationItem? { + didSet { + if let item = self.item { + self.itemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: self.previousItem) + self.itemWrapper?.backPressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + } else { + self.itemWrapper = nil + } + } + } + + private var itemWrapper: NavigationItemWrapper? private let stripeHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale - //private let effectView: UIVisualEffectView - var backPressed: () -> () = { } private var collapsed: Bool { @@ -26,34 +44,6 @@ public class NavigationBar: ASDisplayNode { } } - var _proxy: NavigationBarProxy? - var proxy: NavigationBarProxy? { - get { - return self._proxy - } - set(value) { - self._proxy = value - self._proxy?.setItemsProxy = {[weak self] previousItems, items, animated in - if let strongSelf = self { - var animation = ItemAnimation.None - if animated && previousItems.count != 0 && items.count != 0 { - if previousItems.filter({element in element === items[items.count - 1]}).count != 0 { - animation = .Pop - } - else { - animation = .Push - } - } - - let count = items.count - if count != 0 { - strongSelf.updateTopItem(items[count - 1] as! UINavigationItem, previousItem: count >= 2 ? (items[count - 2] as! UINavigationItem) : nil, animation: animation) - } - } - return - } - } - } let stripeView: UIView public override init() { @@ -70,7 +60,7 @@ public class NavigationBar: ASDisplayNode { self.view.addSubview(stripeView) } - private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { + /*private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { if self.topItem !== item { let previousTopItemWrapper = self.topItemWrapper self.topItemWrapper = nil @@ -116,14 +106,12 @@ public class NavigationBar: ASDisplayNode { if let topItemWrapper = self.topItemWrapper { self.tempItemWrapper?.setInteractivePopProgress(progress, previousItemWrapper: topItemWrapper) } - } + }*/ public override func layout() { - self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height, width: self.frame.size.width, height: stripeHeight) - self.topItemWrapper?.layoutItems() - self.tempItemWrapper?.layoutItems() + self.itemWrapper?.layoutItems() //self.effectView.frame = self.bounds } diff --git a/Display/NavigationBarTransitionContainer.swift b/Display/NavigationBarTransitionContainer.swift new file mode 100644 index 0000000000..fe65e3e534 --- /dev/null +++ b/Display/NavigationBarTransitionContainer.swift @@ -0,0 +1,6 @@ +import Foundation +import AsyncDisplayKit + +class NavigationBarTransitionContainer: ASDisplayNode { + +} diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 80c63b657a..8871bf83ed 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -62,34 +62,34 @@ public class NavigationButtonNode: ASTextNode { return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) } - public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { super.touchesBegan(touches, withEvent: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } - public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { super.touchesMoved(touches, withEvent: event) - self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } - public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { super.touchesEnded(touches, withEvent: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) - if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } - public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { super.touchesCancelled(touches, withEvent: event) - self.touchCount = max(0, self.touchCount - touches.count) + self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index e173ccce45..1eadd5a0e7 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -8,8 +8,9 @@ private struct NavigationControllerLayout { let statusBarHeight: CGFloat } -public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate { - private var _navigationBar: NavigationBar! +public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate, StatusBarSurfaceProvider { + + private let statusBarSurface: StatusBarSurface = StatusBarSurface() private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() @@ -19,27 +20,30 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr private var layout: NavigationControllerLayout? private var pendingLayout: (NavigationControllerLayout, NSTimeInterval, Bool)? - public override init() { - self._navigationBar = nil - - super.init() - - self._navigationBar = NavigationBar() + private var _presentedViewController: UIViewController? + public override var presentedViewController: UIViewController? { + return self._presentedViewController + } - self._navigationBar.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) - self._navigationBar.proxy = self.navigationBar as? NavigationBarProxy - self._navigationBar.backPressed = { [weak self] in - if let strongSelf = self { - if strongSelf.viewControllers.count > 1 { - strongSelf.popViewControllerAnimated(true) - } - } - return + private var _viewControllers: [UIViewController] = [] + public override var viewControllers: [UIViewController] { + get { + return self._viewControllers + } set(value) { + self.setViewControllers(_viewControllers, animated: false) } + } + + public override var topViewController: UIViewController? { + return self._viewControllers.last + } + + public override init() { + super.init() self.statusBarChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationWillChangeStatusBarFrameNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak self] notification in if let strongSelf = self { - let statusBarHeight: CGFloat = (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.CGRectValue().height ?? 20.0 + let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.CGRectValue().height ?? 20.0) let previousLayout: NavigationControllerLayout? if let pendingLayout = strongSelf.pendingLayout { @@ -48,7 +52,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr previousLayout = strongSelf.layout } - strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true) + strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: statusBarHeight), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true) strongSelf.view.setNeedsLayout() } @@ -64,12 +68,13 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } public override func loadView() { - super.loadView() + self.view = UIView() + //super.loadView() + self.view.clipsToBounds = true - self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) self.navigationBar.removeFromSuperview() - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("panGesture:")) + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) panRecognizer.delegate = self panRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(panRecognizer) @@ -91,10 +96,19 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr bottomController.viewWillAppear(true) let bottomView = bottomController.view - let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator + var bottomStatusBar: StatusBar? + if let bottomController = bottomController as? ViewController { + bottomStatusBar = bottomController.statusBar + } - self._navigationBar.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil) + if let bottomStatusBar = bottomStatusBar { + self.statusBarSurface.insertStatusBar(bottomStatusBar, atIndex: 0) + } + + (self.view.window as? Window)?.updateStatusBars() + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator } case UIGestureRecognizerState.Changed: if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { @@ -109,7 +123,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr navigationTransitionCoordinator.animateCompletion(velocity, completion: { self.navigationTransitionCoordinator = nil - self._navigationBar.endInteractivePopProgress() + //self._navigationBar.endInteractivePopProgress() if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -123,6 +137,12 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidDisappear(true) bottomController.viewDidAppear(true) + + if let topStatusBar = (topController as? ViewController)?.statusBar { + self.statusBarSurface.removeStatusBar(topStatusBar) + } + + (self.view.window as? Window)?.updateStatusBars() } }) } @@ -138,7 +158,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr navigationTransitionCoordinator.animateCancel({ self.navigationTransitionCoordinator = nil - self._navigationBar.endInteractivePopProgress() + //self._navigationBar.endInteractivePopProgress() if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -146,6 +166,11 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidAppear(true) bottomController.viewDidDisappear(true) + + if let bottomStatusBar = (bottomController as? ViewController)?.statusBar { + self.statusBarSurface.removeStatusBar(bottomStatusBar) + } + (self.view.window as? Window)?.updateStatusBars() } }) } @@ -169,6 +194,11 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidAppear(true) bottomController.viewDidDisappear(true) + + if let bottomStatusBar = (bottomController as? ViewController)?.statusBar { + self.statusBarSurface.removeStatusBar(bottomStatusBar) + } + (self.view.window as? Window)?.updateStatusBars() } }) } @@ -182,7 +212,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if let currentLayout = self.layout { layout = currentLayout } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0), statusBarHeight: 20.0) + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) } controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in @@ -220,7 +250,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if let currentLayout = self.layout { layout = currentLayout } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0), statusBarHeight: 20.0) + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) } controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) @@ -229,17 +259,83 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - super.setViewControllers(viewControllers, animated: animated) - } - - private func navigationBarFrame(layout: NavigationControllerLayout) -> CGRect { - return CGRect(x: 0.0, y: layout.statusBarHeight - 20.0, width: layout.layout.size.width, height: 20.0 + (layout.layout.size.height >= layout.layout.size.width ? 44.0 : 32.0)) + if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! { + let topController = viewControllers.last! as UIViewController + let bottomController = self.viewControllers.last! as UIViewController + + if let topController = topController as? ViewController { + topController.navigationBar.previousItem = bottomController.navigationItem + } + + bottomController.viewWillDisappear(true) + let bottomView = bottomController.view + topController.viewWillAppear(true) + let topView = topController.view + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + if let topController = topController as? ViewController { + self.statusBarSurface.addStatusBar(topController.statusBar) + } + (self.view.window as? Window)?.updateStatusBars() + + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.navigationTransitionCoordinator = nil + + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + strongSelf.setViewControllers(viewControllers, animated: false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + bottomController.viewDidDisappear(true) + topController.viewDidAppear(true) + + if let bottomController = bottomController as? ViewController { + strongSelf.statusBarSurface.removeStatusBar(bottomController.statusBar) + } + (strongSelf.view.window as? Window)?.updateStatusBars() + } + }) + } else { + var previousStatusBar: StatusBar? + if let previousController = self.viewControllers.last as? ViewController { + previousStatusBar = previousController.statusBar + } + var newStatusBar: StatusBar? + if let newController = viewControllers.last as? ViewController { + newStatusBar = newController.statusBar + } + + if previousStatusBar !== newStatusBar { + if let previousStatusBar = previousStatusBar { + self.statusBarSurface.removeStatusBar(previousStatusBar) + } + if let newStatusBar = newStatusBar { + self.statusBarSurface.addStatusBar(newStatusBar) + } + } + + if let topController = self.viewControllers.last where topController.isViewLoaded() { + topController.navigation_setNavigationController(nil) + topController.view.removeFromSuperview() + } + + self._viewControllers = viewControllers + + if let topController = viewControllers.last { + topController.navigation_setNavigationController(self) + self.view.addSubview(topController.view) + } + + //super.setViewControllers(viewControllers, animated: animated) + } } private func childControllerLayoutForLayout(layout: NavigationControllerLayout) -> ViewControllerLayout { - var insets = layout.layout.insets - insets.top = self.navigationBarFrame(layout).maxY - return ViewControllerLayout(size: layout.layout.size, insets: insets, inputViewHeight: 0.0) + return ViewControllerLayout(size: layout.layout.size, insets: layout.layout.insets, inputViewHeight: 0.0, statusBarHeight: layout.statusBarHeight) } public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { @@ -250,7 +346,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr previousLayout = self.layout } - self.pendingLayout = (NavigationControllerLayout(layout: layout, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false) + self.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false) self.view.setNeedsLayout() } @@ -268,12 +364,12 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr self.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) } - if pendingLayout.1 > DBL_EPSILON { + /*if pendingLayout.1 > DBL_EPSILON { animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(pendingLayout.0), duration: pendingLayout.1) } else { self._navigationBar.frame = self.navigationBarFrame(pendingLayout.0) - } + }*/ if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) @@ -282,13 +378,20 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController if let controller = bottomController as? WindowContentController { + let layout: NavigationControllerLayout + if let currentLayout = self.layout { + layout = currentLayout + } else { + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) + } + controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) } else { bottomController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) } } - self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress) + //self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress) } if let topViewController = self.topViewController { @@ -299,6 +402,14 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } + if let presentedViewController = self.presentedViewController { + if let controller = presentedViewController as? WindowContentController { + controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) + } else { + presentedViewController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) + } + } + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { navigationTransitionCoordinator.updateProgress() } @@ -307,6 +418,72 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } + override public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + if let controller = viewControllerToPresent as? NavigationController { + controller.navigation_setPresentingViewController(self) + self._presentedViewController = controller + + self.view.endEditing(true) + + let layout: NavigationControllerLayout + if let currentLayout = self.layout { + layout = currentLayout + } else { + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) + } + + controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) + + if flag { + controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) + self.view.addSubview(controller.view) + (self.view.window as? Window)?.updateStatusBars() + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = self.view.bounds + (self.view.window as? Window)?.updateStatusBars() + }, completion: { _ in + if let completion = completion { + completion() + } + }) + } else { + self.view.addSubview(controller.view) + (self.view.window as? Window)?.updateStatusBars() + + if let completion = completion { + completion() + } + } + } else { + preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.") + } + } + + override public func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) { + if let controller = self.presentedViewController { + + if flag { + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) + (self.view.window as? Window)?.updateStatusBars() + }, completion: { _ in + controller.view.removeFromSuperview() + self._presentedViewController = nil + (self.view.window as? Window)?.updateStatusBars() + if let completion = completion { + completion() + } + }) + } else { + self._presentedViewController = nil + (self.view.window as? Window)?.updateStatusBars() + if let completion = completion { + completion() + } + } + } + } + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } @@ -314,4 +491,12 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return otherGestureRecognizer is UIPanGestureRecognizer } + + func statusBarSurfaces() -> [StatusBarSurface] { + var surfaces: [StatusBarSurface] = [self.statusBarSurface] + if let controller = self.presentedViewController as? StatusBarSurfaceProvider { + surfaces.appendContentsOf(controller.statusBarSurfaces()) + } + return surfaces + } } diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift index 4855745fb5..f7185c4166 100644 --- a/Display/NavigationItemWrapper.swift +++ b/Display/NavigationItemWrapper.swift @@ -207,7 +207,7 @@ internal class NavigationItemWrapper { rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! } - self.titleNode.measure(CGSize(width: self.parentNode.bounds.size.width - 140.0, height: CGFloat.max)) + self.titleNode.measure(CGSize(width: max(0.0, self.parentNode.bounds.size.width - 140.0), height: CGFloat.max)) self.titleNode.frame = self.titleFrame } diff --git a/Display/NavigationShadow@2x.png b/Display/NavigationShadow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3eecc232e55a751731d3dc7d6c3899239c03daf8 GIT binary patch literal 1116 zcmZ{j&x_MQ6vwBms2f>Og!Km^AznmiCO@_=4QX(<(Qd)6wN^+y$Y$H=8f-E#nWfo< zz39oKR~7N52U!muz1EAcqFw}L{{Rmnh*y8ir0q7wHZaM|d*9Ezd2jOWSC>mO+(`}q zFjHPKYxK*rad1C)N`k95vy`p zYhqWlTX+?Tk?T`60D7d+sf&osM{dUpwWuJ(3XRU0847$XA-4;HRjKkt93Va?&WVy> za6HfJL94CR%u55Dt_s2}LVOLvZnrCTRS^ekP|oM`P*R|xWT_$>Zg|9wvR-(mzsYbP z6NSyd@ri>yp6zR|;&oCG1QuxT8JyGg_X2t0AS;?5jBFpuq6Ei5#A%O?_YcK96EPGy z6ENoKV{tM1XVhs$wF+)I?G4i=$WSEtd{$Dk@?2e3G)dLw^EymvMj6I*F+euKK^^0c zQFS8JaoLhrY2B4Rn^YuVmWOsFa1bppE2AOn@IPBxU&O8-pfF_m+(4hSrS&xw;?6qT z{<`N7gPtf3kus;L#>j8X51FJf4OruVq%i@oi_qdqS*GJ6FV01!QN}vbU=eq4aLu=y zh}A1b5~j2|4%3(vCgB)>I%NNH;@|E!PHd%}V{r6Fhh1oJ-%k|p(8jEk&4s!({q6k% zN88=}@M3oNPcQf2?9Hb;$9h+9^j^)PBlzv>wVltuc7N1QKifL@Y3s?CkM~}_IdJ#_ b$m~nLR;Ef#K;Wa_Io9&!i_7Nr;>z8>-6&NQ literal 0 HcmV?d00001 diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 4b6b98cec9..537c45a4c9 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -17,7 +17,7 @@ public class NavigationTitleNode: ASDisplayNode { public init(text: NSString) { self.label = ASTextNode() - self.label.maximumLineCount = 1 + self.label.maximumNumberOfLines = 1 self.label.truncationMode = .ByTruncatingTail self.label.displaysAsynchronously = false diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift new file mode 100644 index 0000000000..c5fe59ba06 --- /dev/null +++ b/Display/NavigationTransitionCoordinator.swift @@ -0,0 +1,158 @@ +import UIKit + +enum NavigationTransition { + case Push + case Pop +} + +private let shadowWidth: CGFloat = 16.0 + +private func generateShadow() -> UIImage? { + return UIImage(named: "NavigationShadow", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil)?.precomposed().resizableImageWithCapInsets(UIEdgeInsetsZero, resizingMode: .Tile) +} + +private let shadowImage = generateShadow() + +class NavigationTransitionCoordinator { + private var _progress: CGFloat = 0.0 + var progress: CGFloat { + get { + return self._progress + } + set(value) { + self._progress = value + self.updateProgress() + } + } + + private let container: UIView + private let transition: NavigationTransition + private let topView: UIView + private let viewSuperview: UIView? + private let bottomView: UIView + private let topNavigationBar: NavigationBar? + private let bottomNavigationBar: NavigationBar? + private let dimView: UIView + private let shadowView: UIImageView + + init(transition: NavigationTransition, container: UIView, topView: UIView, topNavigationBar: NavigationBar?, bottomView: UIView, bottomNavigationBar: NavigationBar?) { + self.transition = transition + self.container = container + self.topView = topView + switch transition { + case .Push: + self.viewSuperview = bottomView.superview + case .Pop: + self.viewSuperview = topView.superview + } + self.bottomView = bottomView + self.topNavigationBar = topNavigationBar + self.bottomNavigationBar = bottomNavigationBar + self.dimView = UIView() + self.dimView.backgroundColor = UIColor.blackColor() + self.shadowView = UIImageView(image: shadowImage) + + switch transition { + case .Push: + self.viewSuperview?.insertSubview(topView, belowSubview: topView) + case .Pop: + self.viewSuperview?.insertSubview(bottomView, belowSubview: topView) + } + + self.viewSuperview?.insertSubview(self.dimView, belowSubview: topView) + self.viewSuperview?.insertSubview(self.shadowView, belowSubview: dimView) + + self.updateProgress() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateProgress() { + let position: CGFloat + switch self.transition { + case .Push: + position = 1.0 - progress + case .Pop: + position = progress + } + self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) + self.dimView.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, self.topView.frame.minX), height: self.container.bounds.size.height)) + self.shadowView.frame = CGRect(origin: CGPoint(x: self.dimView.frame.maxX - shadowWidth, y: 0.0), size: CGSize(width: shadowWidth, height: self.container.bounds.size.height)) + self.dimView.alpha = (1.0 - position) * 0.15 + self.shadowView.alpha = (1.0 - position) * 0.9 + self.bottomView.frame = CGRect(origin: CGPoint(x: ((position - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) + + (self.container.window as? Window)?.updateStatusBars() + } + + func animateCancel(completion: () -> ()) { + UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 0.0 + }) { (completed) -> Void in + switch self.transition { + case .Push: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.bottomView) + } else { + self.bottomView.removeFromSuperview() + } + self.topView.removeFromSuperview() + case .Pop: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.topView) + } else { + self.topView.removeFromSuperview() + } + self.bottomView.removeFromSuperview() + } + + self.dimView.removeFromSuperview() + self.shadowView.removeFromSuperview() + + completion() + } + } + + func animateCompletion(velocity: CGFloat, completion: () -> ()) { + let distance = (1.0 - self.progress) * self.container.bounds.size.width + let f = { + switch self.transition { + case .Push: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.bottomView) + } else { + self.bottomView.removeFromSuperview() + } + //self.topView.removeFromSuperview() + case .Pop: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.topView) + } else { + self.topView.removeFromSuperview() + } + //self.bottomView.removeFromSuperview() + } + + self.dimView.removeFromSuperview() + self.shadowView.removeFromSuperview() + + completion() + } + + if abs(velocity) < CGFloat(FLT_EPSILON) && abs(self.progress) < CGFloat(FLT_EPSILON) { + UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + self.progress = 1.0 + }, completion: { _ in + f() + }) + } else { + UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 1.0 + }) { (completed) -> Void in + f() + } + } + } +} diff --git a/Display/NavigationTransitionView.swift b/Display/NavigationTransitionView.swift deleted file mode 100644 index 46d081c2d6..0000000000 --- a/Display/NavigationTransitionView.swift +++ /dev/null @@ -1,82 +0,0 @@ -import UIKit - -class NavigationTransitionCoordinator { - private var _progress: CGFloat = 0.0 - var progress: CGFloat { - get { - return self._progress - } - set(value) { - self._progress = value - self.navigationBar.setInteractivePopProgress(value) - self.updateProgress() - } - } - - private let container: UIView - private let topView: UIView - private let topViewSuperview: UIView? - private let bottomView: UIView - private let dimView: UIView - private let navigationBar: NavigationBar - - init(container: UIView, topView: UIView, bottomView: UIView, navigationBar: NavigationBar) { - self.container = container - self.topView = topView - self.topViewSuperview = topView.superview - self.bottomView = bottomView - self.dimView = UIView() - self.dimView.backgroundColor = UIColor.blackColor() - self.navigationBar = navigationBar - - if let topViewSuperview = self.topViewSuperview { - topViewSuperview.insertSubview(bottomView, belowSubview: topView) - } - - self.updateProgress() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func updateProgress() { - self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.progress * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) - self.dimView.frame = self.container.bounds - self.dimView.alpha = (1.0 - self.progress) * 0.1 - self.bottomView.frame = CGRect(origin: CGPoint(x: ((self.progress - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) - } - - func animateCancel(completion: () -> ()) { - UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in - self.progress = 0.0 - }) { (completed) -> Void in - if let topViewSuperview = self.topViewSuperview { - topViewSuperview.addSubview(self.topView) - } - else { - self.topView.removeFromSuperview() - } - self.bottomView.removeFromSuperview() - - completion() - } - } - - func animateCompletion(velocity: CGFloat, completion: () -> ()) { - let distance = (1.0 - self.progress) * self.container.bounds.size.width - UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in - self.progress = 1.0 - }) { (completed) -> Void in - if let topViewSuperview = self.topViewSuperview { - topViewSuperview.addSubview(self.topView) - } - else { - self.topView.removeFromSuperview() - } - self.bottomView.removeFromSuperview() - - completion() - } - } -} \ No newline at end of file diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h index 6742a0f49d..1bd387edbf 100644 --- a/Display/RuntimeUtils.h +++ b/Display/RuntimeUtils.h @@ -8,6 +8,7 @@ typedef enum { @interface RuntimeUtils : NSObject + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector; + (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; @end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m index 3302e14754..2a1b4376f2 100644 --- a/Display/RuntimeUtils.m +++ b/Display/RuntimeUtils.m @@ -18,6 +18,16 @@ } } ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector { + Method origMethod = nil, newMethod = nil; + + origMethod = class_getInstanceMethod(targetClass, currentSelector); + newMethod = class_getInstanceMethod(anotherClass, newSelector); + if ((origMethod != nil) && (newMethod != nil)) { + method_exchangeImplementations(origMethod, newMethod); + } +} + + (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector { Method origMethod = nil, newMethod = nil; diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift new file mode 100644 index 0000000000..50fd47dd33 --- /dev/null +++ b/Display/StatusBar.swift @@ -0,0 +1,59 @@ +import Foundation +import AsyncDisplayKit + +public class StatusBarSurface { + var statusBars: [StatusBar] = [] + + func addStatusBar(statusBar: StatusBar) { + self.removeStatusBar(statusBar) + self.statusBars.append(statusBar) + } + + func insertStatusBar(statusBar: StatusBar, atIndex index: Int) { + self.removeStatusBar(statusBar) + self.statusBars.insert(statusBar, atIndex: index) + } + + func removeStatusBar(statusBar: StatusBar) { + for i in 0 ..< self.statusBars.count { + if self.statusBars[i] === statusBar { + self.statusBars.removeAtIndex(i) + break + } + } + } +} + +public class StatusBar: ASDisplayNode { + public var style: StatusBarStyle = .Black + var proxyNode: StatusBarProxyNode? + + override init() { + super.init() + + self.clipsToBounds = true + self.userInteractionEnabled = false + } + + func removeProxyNode() { + self.proxyNode?.hidden = true + self.proxyNode?.removeFromSupernode() + self.proxyNode = nil + } + + func updateProxyNode() { + let origin = self.view.convertPoint(CGPoint(), toView: nil) + if let proxyNode = proxyNode { + proxyNode.style = self.style + } else { + self.proxyNode = StatusBarProxyNode(style: self.style) + self.proxyNode!.hidden = false + self.addSubnode(self.proxyNode!) + } + + let frame = CGRect(origin: CGPoint(x: -origin.x, y: -origin.y), size: self.proxyNode!.frame.size) + self.proxyNode?.frame = frame + } + + +} diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift new file mode 100644 index 0000000000..a7bf31e6f5 --- /dev/null +++ b/Display/StatusBarManager.swift @@ -0,0 +1,151 @@ +import Foundation +import AsyncDisplayKit + +private struct MappedStatusBar { + let style: StatusBarStyle + let frame: CGRect + let statusBar: StatusBar? +} + +private struct MappedStatusBarSurface { + let statusBars: [MappedStatusBar] + let surface: StatusBarSurface +} + +private func mapStatusBar(statusBar: StatusBar) -> MappedStatusBar { + let frame = CGRect(origin: statusBar.view.convertPoint(CGPoint(), toView: nil), size: statusBar.frame.size) + return MappedStatusBar(style: statusBar.style, frame: frame, statusBar: statusBar) +} + +private func mappedSurface(surface: StatusBarSurface) -> MappedStatusBarSurface { + return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface) +} + +private func optimizeMappedSurface(surface: MappedStatusBarSurface) -> MappedStatusBarSurface { + if surface.statusBars.count > 1 { + for i in 1 ..< surface.statusBars.count { + if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { + return surface + } + } + let size = UIApplication.sharedApplication().statusBarFrame.size + return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) + } else { + return surface + } +} + +private func displayHiddenAnimation() -> CAAnimation { + let animation = CABasicAnimation(keyPath: "transform.translation.y") + animation.fromValue = NSNumber(float: -40.0) + animation.toValue = NSNumber(float: -40.0) + animation.fillMode = kCAFillModeBoth + animation.duration = 100000000.0 + animation.additive = true + animation.removedOnCompletion = false + + return animation +} + +class StatusBarManager { + var surfaces: [StatusBarSurface] = [] { + didSet { + self.updateSurfaces(oldValue) + } + } + + private func updateSurfaces(previousSurfaces: [StatusBarSurface]) { + let mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(mappedSurface($0)) }) + + var visibleStatusBars: [StatusBar] = [] + + var globalStatusBar: (StatusBarStyle, CGFloat)? + for i in 0 ..< mappedSurfaces.count { + if i == mappedSurfaces.count - 1 && mappedSurfaces[i].statusBars.count == 1 { + globalStatusBar = (mappedSurfaces[i].statusBars[0].style, mappedSurfaces[i].statusBars[0].frame.origin.y) + } else { + for mappedStatusBar in mappedSurfaces[i].statusBars { + if let statusBar = mappedStatusBar.statusBar { + visibleStatusBars.append(statusBar) + } + } + } + } + + for surface in previousSurfaces { + for statusBar in surface.statusBars { + if !visibleStatusBars.contains({$0 === statusBar}) { + statusBar.removeProxyNode() + } + } + } + + for surface in self.surfaces { + for statusBar in surface.statusBars { + if !visibleStatusBars.contains({$0 === statusBar}) { + statusBar.removeProxyNode() + } + } + } + + for statusBar in visibleStatusBars { + statusBar.updateProxyNode() + } + + if let globalStatusBar = globalStatusBar { + StatusBarUtils.statusBarWindow()!.hidden = false + let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .Default : .LightContent + if UIApplication.sharedApplication().statusBarStyle != statusBarStyle { + UIApplication.sharedApplication().setStatusBarStyle(statusBarStyle, animated: false) + } + StatusBarUtils.statusBarWindow()!.layer.removeAnimationForKey("displayHidden") + StatusBarUtils.statusBarWindow()!.transform = CGAffineTransformMakeTranslation(0.0, globalStatusBar.1) + } else { + if StatusBarUtils.statusBarWindow()!.layer.animationForKey("displayHidden") == nil { + StatusBarUtils.statusBarWindow()!.layer.addAnimation(displayHiddenAnimation(), forKey: "displayHidden") + } + } + + /*if self.items.count == 1 { + self.shouldHide = true + dispatch_async(dispatch_get_main_queue(), { + if self.shouldHide { + self.items[0].1.hidden = true + self.shouldHide = false + } + }) + //self.items[0].1.hidden = true + StatusBarUtils.statusBarWindow()!.hidden = false + } else if !self.items.isEmpty { + self.shouldHide = false + for (statusBar, node) in self.items { + node.hidden = false + var frame = statusBar.frame + frame.size.width = self.bounds.size.width + frame.size.height = 20.0 + node.frame = frame + + //print("origin: \(frame.origin.x)") + //print("style: \(node.style)") + + let bounds = frame + node.bounds = bounds + } + + UIView.performWithoutAnimation { + StatusBarUtils.statusBarWindow()!.hidden = true + } + } + + var statusBarStyle: UIStatusBarStyle = .Default + if let lastItem = self.items.last { + statusBarStyle = lastItem.0.style == .Black ? .Default : .LightContent + } + + if UIApplication.sharedApplication().statusBarStyle != statusBarStyle { + UIApplication.sharedApplication().setStatusBarStyle(statusBarStyle, animated: false) + } + + //print("window \(StatusBarUtils.statusBarWindow()!)")*/ + } +} \ No newline at end of file diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift new file mode 100644 index 0000000000..0e9f101970 --- /dev/null +++ b/Display/StatusBarProxyNode.swift @@ -0,0 +1,333 @@ +import Foundation +import AsyncDisplayKit + +public enum StatusBarStyle { + case Black + case White +} + +private enum StatusBarItemType { + case Generic + case Battery +} + +func makeStatusBarProxy(style: StatusBarStyle) -> StatusBarProxyNode { + return StatusBarProxyNode(style: style) +} + +private class StatusBarItemNode: ASDisplayNode { + var style: StatusBarStyle + var targetView: UIView + + init(style: StatusBarStyle, targetView: UIView) { + self.style = style + self.targetView = targetView + + super.init() + } + + func update() { + let context = DrawingContext(size: self.targetView.frame.size, clear: true) + + if let contents = self.targetView.layer.contents where (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents) == CGImageGetTypeID() && false { + let image = contents as! CGImageRef + context.withFlippedContext { c in + CGContextSetAlpha(c, CGFloat(self.targetView.layer.opacity)) + CGContextDrawImage(c, CGRect(origin: CGPoint(), size: context.size), image) + CGContextSetAlpha(c, 1.0) + } + + if let sublayers = self.targetView.layer.sublayers { + for sublayer in sublayers { + let origin = sublayer.frame.origin + if let contents = sublayer.contents where CFGetTypeID(contents) == CGImageGetTypeID() { + let image = contents as! CGImageRef + context.withFlippedContext { c in + CGContextTranslateCTM(c, origin.x, origin.y) + CGContextDrawImage(c, CGRect(origin: CGPoint(), size: context.size), image) + CGContextTranslateCTM(c, -origin.x, -origin.y) + } + } else { + context.withContext { c in + UIGraphicsPushContext(c) + CGContextTranslateCTM(c, origin.x, origin.y) + sublayer.renderInContext(c) + CGContextTranslateCTM(c, -origin.x, -origin.y) + UIGraphicsPopContext() + } + } + } + } + } else { + context.withContext { c in + UIGraphicsPushContext(c) + self.targetView.layer.renderInContext(c) + UIGraphicsPopContext() + } + } + + let type: StatusBarItemType = self.targetView.isKindOfClass(batteryItemClass!) ? .Battery : .Generic + tintStatusBarItem(context, type: type, style: style) + self.contents = context.generateImage()?.CGImage + + self.frame = self.targetView.frame + } +} + +private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, style: StatusBarStyle) { + switch type { + case .Battery: + let minY = 0 + let minX = 0 + let maxY = Int(context.size.height * context.scale) + let maxX = Int(context.size.width * context.scale) + if minY < maxY && minX < maxX { + let basePixel = UnsafeMutablePointer(context.bytes) + let pixelsPerRow = context.bytesPerRow / 4 + + let midX = (maxX + minX) / 2 + let midY = (maxY + minY) / 2 + let baseMidRow = basePixel + pixelsPerRow * midY + var baseX = minX + while baseX < maxX { + let pixel = baseMidRow + baseX + let alpha = pixel.memory & 0xff000000 + if alpha != 0 { + break + } + baseX += 1 + } + + baseX += 2 + + var targetX = baseX + while targetX < maxX { + let pixel = baseMidRow + targetX + let alpha = pixel.memory & 0xff000000 + if alpha == 0 { + break + } + + targetX += 1 + } + + let batteryColor = (baseMidRow + baseX).memory + let batteryR = (batteryColor >> 16) & 0xff + let batteryG = (batteryColor >> 8) & 0xff + let batteryB = batteryColor & 0xff + + var baseY = minY + while baseY < maxY { + let baseRow = basePixel + pixelsPerRow * baseY + let pixel = baseRow + midX + let alpha = pixel.memory & 0xff000000 + if alpha != 0 { + break + } + baseY += 1 + } + + var targetY = maxY - 1 + while targetY >= baseY { + let baseRow = basePixel + pixelsPerRow * targetY + let pixel = baseRow + midX + let alpha = pixel.memory & 0xff000000 + if alpha != 0 { + break + } + targetY -= 1 + } + + targetY -= 1 + + let baseColor: UInt32 + switch style { + case .Black: + baseColor = 0x000000 + case .White: + baseColor = 0xffffff + } + + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + var pixel = UnsafeMutablePointer(context.bytes) + let end = UnsafeMutablePointer(context.bytes + context.length) + while pixel != end { + let alpha = (pixel.memory & 0xff000000) >> 24 + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + pixel += 1 + } + + if batteryColor != 0xffffffff && batteryColor != 0xff000000 { + var y = baseY + 2 + while y < targetY { + let baseRow = basePixel + pixelsPerRow * y + var x = baseX + while x < targetX { + let pixel = baseRow + x + let alpha = (pixel.memory >> 24) & 0xff + + let r = (batteryR * alpha) / 255 + let g = (batteryG * alpha) / 255 + let b = (batteryB * alpha) / 255 + + pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + x += 1 + } + y += 1 + } + } + } + case .Generic: + var pixel = UnsafeMutablePointer(context.bytes) + let end = UnsafeMutablePointer(context.bytes + context.length) + + let baseColor: UInt32 + switch style { + case .Black: + baseColor = 0x000000 + case .White: + baseColor = 0xffffff + } + + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + while pixel != end { + let alpha = (pixel.memory & 0xff000000) >> 24 + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + pixel += 1 + } + } +} + +private let batteryItemClass: AnyClass? = NSClassFromString("UIStatusBarBatteryItemView") + +private class StatusBarProxyNodeTimerTarget: NSObject { + let action: () -> Void + + init(action: () -> Void) { + self.action = action + } + + @objc func tick() { + action() + } +} + +class StatusBarProxyNode: ASDisplayNode { + var timer: NSTimer? + var style: StatusBarStyle { + didSet { + if oldValue != self.style { + if !self.hidden { + self.updateItems() + } + } + } + } + + private var itemNodes: [StatusBarItemNode] = [] + + override var hidden: Bool { + get { + return super.hidden + } set(value) { + if super.hidden != value { + super.hidden = value + + if !value { + self.updateItems() + self.timer = NSTimer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in + self?.updateItems() + }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) + NSRunLoop.mainRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes) + } else { + self.timer?.invalidate() + self.timer = nil + } + } + } + } + + init(style: StatusBarStyle) { + self.style = style + + super.init() + + self.hidden = true + + self.clipsToBounds = true + //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) + + let statusBar = StatusBarUtils.statusBar()! + + for subview in statusBar.subviews { + let itemNode = StatusBarItemNode(style: style, targetView: subview) + self.itemNodes.append(itemNode) + self.addSubnode(itemNode) + } + + self.frame = statusBar.bounds + } + + deinit { + self.timer?.invalidate() + } + + private func updateItems() { + let statusBar = StatusBarUtils.statusBar()! + + var i = 0 + while i < self.itemNodes.count { + var found = false + for subview in statusBar.subviews { + if self.itemNodes[i].targetView == subview { + found = true + break + } + } + if !found { + self.itemNodes[i].removeFromSupernode() + self.itemNodes.removeAtIndex(i) + } else { + self.itemNodes[i].style = self.style + self.itemNodes[i].update() + i += 1 + } + } + + for subview in statusBar.subviews { + var found = false + for itemNode in self.itemNodes { + if itemNode.targetView == subview { + found = true + break + } + } + + if !found { + let itemNode = StatusBarItemNode(style: self.style, targetView: subview) + itemNode.update() + self.itemNodes.append(itemNode) + self.addSubnode(itemNode) + } + } + } +} diff --git a/Display/StatusBarSurfaceProvider.swift b/Display/StatusBarSurfaceProvider.swift new file mode 100644 index 0000000000..25e3c9554a --- /dev/null +++ b/Display/StatusBarSurfaceProvider.swift @@ -0,0 +1,4 @@ + +protocol StatusBarSurfaceProvider { + func statusBarSurfaces() -> [StatusBarSurface] +} diff --git a/Display/StatusBarUtils.h b/Display/StatusBarUtils.h new file mode 100644 index 0000000000..45d160cca9 --- /dev/null +++ b/Display/StatusBarUtils.h @@ -0,0 +1,9 @@ +#import +#import + +@interface StatusBarUtils : NSObject + ++ (UIView * _Nullable)statusBarWindow; ++ (UIView * _Nullable)statusBar; + +@end diff --git a/Display/StatusBarUtils.m b/Display/StatusBarUtils.m new file mode 100644 index 0000000000..9890f83561 --- /dev/null +++ b/Display/StatusBarUtils.m @@ -0,0 +1,32 @@ +#import "StatusBarUtils.h" + +@implementation StatusBarUtils + ++ (UIView *)statusBarWindow { + UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; + UIView *view = window.subviews.firstObject; + return view; +} + ++ (UIView *)statusBar { + UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; + UIView *view = window.subviews.firstObject; + + static Class foregroundClass = nil; + static Class batteryClass = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + foregroundClass = NSClassFromString(@"UIStatusBarForegroundView"); + batteryClass = NSClassFromString(@"UIStatusBarBatteryItemView"); + }); + + for (UIView *foreground in view.subviews) { + if ([foreground isKindOfClass:foregroundClass]) { + return foreground; + } + } + + return nil; +} + +@end diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index e587d41674..0d672e8c5e 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -98,7 +98,7 @@ public class TabBarController: ViewController { private func childControllerLayoutForLayout(layout: ViewControllerLayout) -> ViewControllerLayout { var insets = layout.insets insets.bottom += 49.0 - return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight) + return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: layout.statusBarHeight) } override public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 0e7277ba4d..a47407de06 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -97,7 +97,7 @@ class TabBarNode: ASDisplayNode { node.displayWithoutProcessing = true node.layerBacked = true if let selectedIndex = self.selectedIndex where selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor.blueColor()) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } @@ -116,7 +116,7 @@ class TabBarNode: ASDisplayNode { let item = self.tabBarItems[index] if let selectedIndex = self.selectedIndex where selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor.blueColor()) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } @@ -145,10 +145,10 @@ class TabBarNode: ASDisplayNode { } } - override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + override func touchesBegan(touches: Set, withEvent event: UIEvent?) { super.touchesBegan(touches, withEvent: event) - if let touch = touches.first as? UITouch { + if let touch = touches.first { let location = touch.locationInView(self.view) var closestNode: (Int, CGFloat)? diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index e551655f77..9d1aefdb6b 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -59,7 +59,7 @@ public extension CGSize { } } -public func assertNotOnMainThread(file: String = __FILE__, line: Int = __LINE__) { +public func assertNotOnMainThread(file: String = #file, line: Int = #line) { assert(!NSThread.isMainThread(), "\(file):\(line) running on main thread") } diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 8f5a15d055..55de13b6c3 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -3,5 +3,9 @@ @interface UIViewController (Navigation) - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; +- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; +- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; @end + +void applyKeyboardAutocorrection(); diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 565c3ec385..b0b98adac8 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -1,8 +1,59 @@ #import "UIViewController+Navigation.h" #import "RuntimeUtils.h" +#import + +#import "NSWeakReference.h" static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIViewControllerIgnoreAppearanceMethodInvocationsKey; +static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNavigationControllerKey; +static const void *UIViewControllerPresentingViewControllerKey = &UIViewControllerPresentingViewControllerKey; + +static bool notyfyingShiftState = false; + +@interface UIKeyboardImpl_65087dc8: UIView + +@end + +@implementation UIKeyboardImpl_65087dc8 + +- (void)notifyShiftState { + static void (*impl)(id, SEL) = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method m = class_getInstanceMethod([UIKeyboardImpl_65087dc8 class], @selector(notifyShiftState)); + impl = (typeof(impl))method_getImplementation(m); + }); + if (impl) { + notyfyingShiftState = true; + impl(self, @selector(notifyShiftState)); + notyfyingShiftState = false; + } +} + +@end + +@interface UIInputWindowController_65087dc8: UIViewController + +@end + +@implementation UIInputWindowController_65087dc8 + +- (void)updateViewConstraints { + static void (*impl)(id, SEL) = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method m = class_getInstanceMethod([UIInputWindowController_65087dc8 class], @selector(updateViewConstraints)); + impl = (typeof(impl))method_getImplementation(m); + }); + if (impl) { + if (!notyfyingShiftState) { + impl(self, @selector(updateViewConstraints)); + } + } +} + +@end @implementation UIViewController (Navigation) @@ -15,6 +66,11 @@ static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIVie [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidAppear:) newSelector:@selector(_65087dc8_viewDidAppear:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewWillDisappear:) newSelector:@selector(_65087dc8_viewWillDisappear:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidDisappear:) newSelector:@selector(_65087dc8_viewDidDisappear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(navigationController) newSelector:@selector(_65087dc8_navigationController)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)]; + + //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)]; + //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)]; }); } @@ -52,4 +108,73 @@ static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIVie [self _65087dc8_viewDidDisappear:animated]; } +- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller { + [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:navigationControlller] forKey:UIViewControllerNavigationControllerKey]; +} + +- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController { + [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:presentingViewController] forKey:UIViewControllerPresentingViewControllerKey]; +} + +- (UINavigationController *)_65087dc8_navigationController { + UINavigationController *navigationController = self._65087dc8_navigationController; + if (navigationController != nil) { + return navigationController; + } + + navigationController = self.parentViewController.navigationController; + if (navigationController != nil) { + return navigationController; + } + + return ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerNavigationControllerKey]).value; +} + +- (UIViewController *)_65087dc8_presentingViewController { + UINavigationController *navigationController = self.navigationController; + if (navigationController.presentingViewController != nil) { + return navigationController.presentingViewController; + } + + return ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerPresentingViewControllerKey]).value; +} + @end + +static NSString *TGEncodeText(NSString *string, int key) +{ + NSMutableString *result = [[NSMutableString alloc] init]; + + for (int i = 0; i < (int)[string length]; i++) + { + unichar c = [string characterAtIndex:i]; + c += key; + [result appendString:[NSString stringWithCharacters:&c length:1]]; + } + + return result; +} + +void applyKeyboardAutocorrection() { + static Class keyboardClass = NULL; + static SEL currentInstanceSelector = NULL; + static SEL applyVariantSelector = NULL; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1)); + + currentInstanceSelector = NSSelectorFromString(TGEncodeText(@"bdujwfLfzcpbse", -1)); + applyVariantSelector = NSSelectorFromString(TGEncodeText(@"bddfquBvupdpssfdujpo", -1)); + }); + + if ([keyboardClass respondsToSelector:currentInstanceSelector]) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id currentInstance = [keyboardClass performSelector:currentInstanceSelector]; + if ([currentInstance respondsToSelector:applyVariantSelector]) + [currentInstance performSelector:applyVariantSelector]; +#pragma clang diagnostic pop + } +} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index c74892c441..26b71c7278 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit import SwiftSignalKit public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { - return lhs.size == rhs.size && lhs.insets == rhs.insets && lhs.inputViewHeight == rhs.inputViewHeight + return lhs.size == rhs.size && lhs.insets == rhs.insets && lhs.inputViewHeight == rhs.inputViewHeight && lhs.statusBarHeight == rhs.statusBarHeight } @objc public class ViewController: UIViewController, WindowContentController { @@ -31,6 +31,9 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { return self._displayNode != nil } + public let statusBar: StatusBar + public let navigationBar: NavigationBar + private let _ready = Promise(true) public var ready: Promise { return self._ready @@ -42,8 +45,12 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { var keyboardFrameObserver: AnyObject? public init() { + self.statusBar = StatusBar() + self.navigationBar = NavigationBar() + super.init(nibName: nil, bundle: nil) + self.navigationBar.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false self.keyboardFrameObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil, usingBlock: { [weak self] notification in @@ -61,7 +68,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } else{ previousLayout = strongSelf.layout } - let layout = ViewControllerLayout(size: previousLayout?.size ?? CGSize(), insets: previousLayout?.insets ?? UIEdgeInsets(), inputViewHeight: keyboardHeight) + let layout = ViewControllerLayout(size: previousLayout?.size ?? CGSize(), insets: previousLayout?.insets ?? UIEdgeInsets(), inputViewHeight: keyboardHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0) let updated: Bool if let previousLayout = previousLayout { updated = previousLayout != layout @@ -69,7 +76,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { updated = true } if updated { - print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") + //print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration > DBL_EPSILON ? 0.5 : 0.0, curve) strongSelf.updateLayoutOnLayout = (layout, durationAndCurve.0, durationAndCurve.1) @@ -91,10 +98,11 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { public override func loadView() { self.view = self.displayNode.view + self.displayNode.addSubnode(self.navigationBar) + self.view.addSubview(self.statusBar.view) } public func loadDisplayNode() { - self.displayNode = ASDisplayNode() } @@ -102,6 +110,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { if self._displayNode == nil { self.loadDisplayNode() } + let previousLayout: ViewControllerLayout? if let updateLayoutOnLayout = self.updateLayoutOnLayout { previousLayout = updateLayoutOnLayout.0 @@ -109,7 +118,10 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { previousLayout = self.layout } - let layout = ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: previousLayout?.inputViewHeight ?? 0.0) + var insets = layout.insets + insets.top += 22.0 + + let layout = ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: previousLayout?.inputViewHeight ?? 0.0, statusBarHeight: layout.statusBarHeight) let updated: Bool if let previousLayout = previousLayout { updated = previousLayout != layout @@ -128,6 +140,8 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + self.navigationBar.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 44.0 + 20.0)) } override public func viewDidLayoutSubviews() { @@ -138,6 +152,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { let previousLayout = self.layout self.layout = updateLayoutOnLayout.0 self.updateLayout(updateLayoutOnLayout.0, previousLayout: previousLayout, duration: updateLayoutOnLayout.1, curve: updateLayoutOnLayout.2) + self.view.frame = CGRect(origin: self.view.frame.origin, size: updateLayoutOnLayout.0.size) self.updateLayoutOnLayout = nil } else { @@ -156,4 +171,12 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } } } + + override public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + if let navigationController = self.navigationController as? NavigationController { + navigationController.presentViewController(viewControllerToPresent, animated: flag, completion: completion) + } else { + super.presentViewController(viewControllerToPresent, animated: flag, completion: completion) + } + } } diff --git a/Display/Window.swift b/Display/Window.swift index 4fccbff987..2ed18955cb 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -15,6 +15,7 @@ public struct ViewControllerLayout: Equatable { public let size: CGSize public let insets: UIEdgeInsets public let inputViewHeight: CGFloat + public let statusBarHeight: CGFloat } public protocol WindowContentController { @@ -74,7 +75,7 @@ public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NST } public class Window: UIWindow { - //public let textField: UITextField + private let statusBarManager: StatusBarManager private var updateViewSizeOnLayout: (Bool, NSTimeInterval) = (false, 0.0) public var isUpdatingOrientationLayout = false @@ -88,12 +89,10 @@ public class Window: UIWindow { } public override init(frame: CGRect) { - //self.textField = UITextField(frame: CGRect(x: -110.0, y: 0.0, width: 100.0, height: 50.0)) + self.statusBarManager = StatusBarManager() super.init(frame: frame) - //self.addSubview(self.textField) - super.rootViewController = WindowRootViewController() } @@ -144,11 +143,13 @@ public class Window: UIWindow { self._rootViewController?.view.removeFromSuperview() self._rootViewController = value self._rootViewController?.view.frame = self.bounds - self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0), duration: 0.0, curve: 0) + self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: 0.0), duration: 0.0, curve: 0) if let view = self._rootViewController?.view { self.addSubview(view) } + + self.updateStatusBars() } } @@ -158,7 +159,7 @@ public class Window: UIWindow { if self.updateViewSizeOnLayout.0 { self.updateViewSizeOnLayout.0 = false - self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0), duration: updateViewSizeOnLayout.1, curve: 0) + self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: 0.0), duration: updateViewSizeOnLayout.1, curve: 0) } } @@ -179,4 +180,8 @@ public class Window: UIWindow { public func addPostUpdateToInterfaceOrientationBlock(f: Void -> Void) { postUpdateToInterfaceOrientationBlocks.append(f) } + + func updateStatusBars() { + self.statusBarManager.surfaces = (self._rootViewController as? StatusBarSurfaceProvider)?.statusBarSurfaces() ?? [] + } }