mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
no message
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheet.swift; sourceTree = "<group>"; };
|
||||
D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = "<group>"; };
|
||||
D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = "<group>"; };
|
||||
D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayTheme.swift; sourceTree = "<group>"; };
|
||||
D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultDisplayTheme.swift; sourceTree = "<group>"; };
|
||||
D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = "<group>"; };
|
||||
D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionContainer.swift; sourceTree = "<group>"; };
|
||||
D03E7DF61C96C5F200C07816 /* NSWeakReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSWeakReference.h; sourceTree = "<group>"; };
|
||||
D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = "<group>"; };
|
||||
D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = "<group>"; };
|
||||
D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = "<group>"; };
|
||||
D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarSurfaceProvider.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -99,7 +129,7 @@
|
||||
D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationCenterUtils.h; sourceTree = "<group>"; };
|
||||
D05CC3051B69575900E235A3 /* NSBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBag.m; sourceTree = "<group>"; };
|
||||
D05CC3061B69575900E235A3 /* NSBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBag.h; sourceTree = "<group>"; };
|
||||
D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionView.swift; sourceTree = "<group>"; };
|
||||
D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionCoordinator.swift; sourceTree = "<group>"; };
|
||||
D05CC30A1B695A9500E235A3 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
|
||||
D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemWrapper.swift; sourceTree = "<group>"; };
|
||||
D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemTransitionState.swift; sourceTree = "<group>"; };
|
||||
@@ -119,6 +149,12 @@
|
||||
D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
|
||||
D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = "<group>"; };
|
||||
D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = "<group>"; };
|
||||
D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarUtils.h; sourceTree = "<group>"; };
|
||||
D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarUtils.m; sourceTree = "<group>"; };
|
||||
D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = "<group>"; };
|
||||
D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = "<group>"; };
|
||||
D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = "<group>"; };
|
||||
D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = "<group>"; };
|
||||
D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = "<group>"; };
|
||||
D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = "<group>"; };
|
||||
D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
};
|
||||
D02BDAEC1B6A7053008AFAD2 /* Nodes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -153,6 +198,15 @@
|
||||
name = Nodes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D03BCCE91C72AE4B0097A291 /* Theme */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */,
|
||||
D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */,
|
||||
);
|
||||
name = Theme;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@@ -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 = "<group>";
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
7
Display/ActionSheet.swift
Normal file
7
Display/ActionSheet.swift
Normal file
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public class ActionSheet {
|
||||
public init() {
|
||||
|
||||
}
|
||||
}
|
||||
8
Display/ActionSheetItemNode.swift
Normal file
8
Display/ActionSheetItemNode.swift
Normal file
@@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public class ActionSheetItemNode: ASDisplayNode {
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
5
Display/DefaultDisplayTheme.swift
Normal file
5
Display/DefaultDisplayTheme.swift
Normal file
@@ -0,0 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
func defaultDisplayTheme() -> DisplayTheme {
|
||||
return DisplayTheme(tintColor: UIColor.blueColor())
|
||||
}
|
||||
@@ -27,4 +27,7 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[];
|
||||
#import <Display/UIBarButtonItem+Proxy.h>
|
||||
#import <Display/NavigationControllerProxy.h>
|
||||
#import <Display/NavigationBarProxy.h>
|
||||
#import <Display/StatusBarUtils.h>
|
||||
#import <UIKit/UIGestureRecognizerSubclass.h>
|
||||
#import <Display/NSWeakReference.h>
|
||||
#import <Display/FBAnimationPerformanceTracker.h>
|
||||
|
||||
34
Display/DisplayLinkDispatcher.swift
Normal file
34
Display/DisplayLinkDispatcher.swift
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Display/DisplayTheme.swift
Normal file
9
Display/DisplayTheme.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
public class DisplayTheme {
|
||||
var tintColor: UIColor
|
||||
|
||||
public init(tintColor: UIColor) {
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
}
|
||||
136
Display/FBAnimationPerformanceTracker.h
Normal file
136
Display/FBAnimationPerformanceTracker.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/*
|
||||
* 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 <NSObject>
|
||||
|
||||
/**
|
||||
* 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<FBAnimationPerformanceTrackerDelegate> delegate;
|
||||
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
|
||||
@end
|
||||
412
Display/FBAnimationPerformanceTracker.mm
Normal file
412
Display/FBAnimationPerformanceTracker.mm
Normal file
@@ -0,0 +1,412 @@
|
||||
//
|
||||
// FBAnimationPerformanceTracker.m
|
||||
// Display
|
||||
//
|
||||
// Created by Peter on 3/16/16.
|
||||
// Copyright © 2016 Telegram. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FBAnimationPerformanceTracker.h"
|
||||
|
||||
#import <dlfcn.h>
|
||||
#import <map>
|
||||
#import <pthread.h>
|
||||
|
||||
#import <QuartzCore/CADisplayLink.h>
|
||||
|
||||
#import <mach-o/dyld.h>
|
||||
|
||||
#import "execinfo.h"
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
static BOOL _signalSetup;
|
||||
static pthread_t _mainThread;
|
||||
static NSThread *_trackerThread;
|
||||
|
||||
static std::map<void *, NSString *, std::greater<void *>> _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<const struct load_command *>(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<const fb_mach_segment_command *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
_imageNames[(void *)(seg->vmaddr + imageSlide)] = imageName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmd = reinterpret_cast<struct load_command*>((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
|
||||
258
Display/GenerateImage.swift
Normal file
258
Display/GenerateImage.swift
Normal file
@@ -0,0 +1,258 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
let deviceColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let deviceScale = UIScreen.mainScreen().scale
|
||||
|
||||
public func generateImage(size: CGSize, generator: (CGSize, UnsafeMutablePointer<Int8>) -> 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<Int8>(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<Int8>(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<Int8>
|
||||
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<Int8>(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<UInt32>(other.bytes + srcY * other.bytesPerRow)
|
||||
let dstLine = UnsafeMutablePointer<UInt32>(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<UInt8>, end: UnsafePointer<UInt8>, 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<Void>(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<UInt8> = 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
9
Display/NSWeakReference.h
Normal file
9
Display/NSWeakReference.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSWeakReference : NSObject
|
||||
|
||||
@property (nonatomic, weak) id value;
|
||||
|
||||
- (instancetype)initWithValue:(id)value;
|
||||
|
||||
@end
|
||||
13
Display/NSWeakReference.m
Normal file
13
Display/NSWeakReference.m
Normal file
@@ -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
|
||||
@@ -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<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
||||
super.touchesBegan(touches, withEvent: event)
|
||||
self.touchCount += touches.count
|
||||
self.updateHighlightedState(true, animated: false)
|
||||
}
|
||||
|
||||
public override func touchesMoved(touches: Set<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesMoved(touches: Set<UITouch>, 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<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesEnded(touches: Set<UITouch>, 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<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesCancelled(touches: Set<UITouch>?, 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,41 @@
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
private enum ItemAnimation {
|
||||
case None
|
||||
case Push
|
||||
case Pop
|
||||
public class NavigationBar: ASDisplayNode {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NavigationBar: ASDisplayNode {
|
||||
private var topItem: UINavigationItem?
|
||||
private var topItemWrapper: 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 tempItem: UINavigationItem?
|
||||
private var tempItemWrapper: NavigationItemWrapper?
|
||||
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
|
||||
}
|
||||
|
||||
6
Display/NavigationBarTransitionContainer.swift
Normal file
6
Display/NavigationBarTransitionContainer.swift
Normal file
@@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
class NavigationBarTransitionContainer: ASDisplayNode {
|
||||
|
||||
}
|
||||
@@ -62,34 +62,34 @@ public class NavigationButtonNode: ASTextNode {
|
||||
return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view))
|
||||
}
|
||||
|
||||
public override func touchesBegan(touches: Set<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
||||
super.touchesBegan(touches, withEvent: event)
|
||||
self.touchCount += touches.count
|
||||
self.updateHighlightedState(true, animated: false)
|
||||
}
|
||||
|
||||
public override func touchesMoved(touches: Set<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesMoved(touches: Set<UITouch>, 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<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesEnded(touches: Set<UITouch>, 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<NSObject>!, withEvent event: UIEvent!) {
|
||||
public override func touchesCancelled(touches: Set<UITouch>?, 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?
|
||||
|
||||
private var _presentedViewController: UIViewController?
|
||||
public override var presentedViewController: UIViewController? {
|
||||
return self._presentedViewController
|
||||
}
|
||||
|
||||
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() {
|
||||
self._navigationBar = nil
|
||||
|
||||
super.init()
|
||||
|
||||
self._navigationBar = NavigationBar()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
BIN
Display/NavigationShadow@2x.png
Normal file
BIN
Display/NavigationShadow@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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
|
||||
|
||||
|
||||
158
Display/NavigationTransitionCoordinator.swift
Normal file
158
Display/NavigationTransitionCoordinator.swift
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
59
Display/StatusBar.swift
Normal file
59
Display/StatusBar.swift
Normal file
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
151
Display/StatusBarManager.swift
Normal file
151
Display/StatusBarManager.swift
Normal file
@@ -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()!)")*/
|
||||
}
|
||||
}
|
||||
333
Display/StatusBarProxyNode.swift
Normal file
333
Display/StatusBarProxyNode.swift
Normal file
@@ -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<UInt32>(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<UInt32>(context.bytes)
|
||||
let end = UnsafeMutablePointer<UInt32>(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<UInt32>(context.bytes)
|
||||
let end = UnsafeMutablePointer<UInt32>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Display/StatusBarSurfaceProvider.swift
Normal file
4
Display/StatusBarSurfaceProvider.swift
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
protocol StatusBarSurfaceProvider {
|
||||
func statusBarSurfaces() -> [StatusBarSurface]
|
||||
}
|
||||
9
Display/StatusBarUtils.h
Normal file
9
Display/StatusBarUtils.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface StatusBarUtils : NSObject
|
||||
|
||||
+ (UIView * _Nullable)statusBarWindow;
|
||||
+ (UIView * _Nullable)statusBar;
|
||||
|
||||
@end
|
||||
32
Display/StatusBarUtils.m
Normal file
32
Display/StatusBarUtils.m
Normal file
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<NSObject>!, withEvent event: UIEvent!) {
|
||||
override func touchesBegan(touches: Set<UITouch>, 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)?
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,8 +1,59 @@
|
||||
#import "UIViewController+Navigation.h"
|
||||
|
||||
#import "RuntimeUtils.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Bool>(true)
|
||||
public var ready: Promise<Bool> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() ?? []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user