├── Demo ├── VHLNavigation_Swift.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── vincent.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── vincent.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── VHLNavigation_Swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── head.imageset │ │ ├── Contents.json │ │ └── head.jpg │ ├── navbg.imageset │ │ ├── Contents.json │ │ └── millcolorGrad.png │ └── vhl_nav_back.imageset │ │ ├── Contents.json │ │ ├── vhl_nav_back@2x.png │ │ └── vhl_nav_back@3x.png │ ├── BGAlphaViewController.swift │ ├── BGColorViewController.swift │ ├── BGHideViewController.swift │ ├── BGImageViewController.swift │ ├── BGViewViewController.swift │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Base │ ├── BaseNavigationC.swift │ └── BaseViewController.swift │ ├── BaseTestViewController.swift │ ├── FakeViewController.swift │ ├── IgnoreViewController.swift │ ├── Info.plist │ ├── NavScrollViewController.swift │ └── VHLNavigation │ └── VHLNavigation.swift ├── LICENSE ├── README.md ├── VHLNavigation └── VHLNavigation.swift ├── VHLNavigation_Swift.podspec └── screenshots ├── 导航栏滚动.gif ├── 微信样式.gif ├── 背景图片.gif ├── 自定义View.gif ├── 透明度.gif ├── 隐藏导航栏.gif └── 颜色过渡.gif /Demo/VHLNavigation_Swift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BF1752F623418DAB005741BF /* FakeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1752F523418DAB005741BF /* FakeViewController.swift */; }; 11 | BF1A1FB42340A928003D89EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1A1FB32340A928003D89EE /* AppDelegate.swift */; }; 12 | BF1A1FBB2340A928003D89EE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF1A1FB92340A928003D89EE /* Main.storyboard */; }; 13 | BF1A1FBD2340A92B003D89EE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF1A1FBC2340A92B003D89EE /* Assets.xcassets */; }; 14 | BF1A1FC02340A92B003D89EE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF1A1FBE2340A92B003D89EE /* LaunchScreen.storyboard */; }; 15 | BF1A1FC92340A977003D89EE /* VHLNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1A1FC82340A977003D89EE /* VHLNavigation.swift */; }; 16 | BF3353E22340F07500D5201A /* BaseNavigationC.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3353E12340F07500D5201A /* BaseNavigationC.swift */; }; 17 | BF3353E42340F08200D5201A /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3353E32340F08200D5201A /* BaseViewController.swift */; }; 18 | BF555C7B235FE01300E624A9 /* BGViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF555C7A235FE01300E624A9 /* BGViewViewController.swift */; }; 19 | BFCF781723614EB2004A888C /* NavScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF781623614EB2004A888C /* NavScrollViewController.swift */; }; 20 | BFCF7819236153B4004A888C /* IgnoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF7818236153B4004A888C /* IgnoreViewController.swift */; }; 21 | BFF03FD0235ED31600A953A0 /* BGImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF03FCF235ED31600A953A0 /* BGImageViewController.swift */; }; 22 | BFF03FD2235ED33100A953A0 /* BaseTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF03FD1235ED33100A953A0 /* BaseTestViewController.swift */; }; 23 | BFF03FD4235ED79700A953A0 /* BGColorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF03FD3235ED79700A953A0 /* BGColorViewController.swift */; }; 24 | BFF03FD6235ED97600A953A0 /* BGAlphaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF03FD5235ED97600A953A0 /* BGAlphaViewController.swift */; }; 25 | BFF03FD8235EDABD00A953A0 /* BGHideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF03FD7235EDABD00A953A0 /* BGHideViewController.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | BF1752F523418DAB005741BF /* FakeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeViewController.swift; sourceTree = ""; }; 30 | BF1A1FB02340A928003D89EE /* VHLNavigation_Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VHLNavigation_Swift.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | BF1A1FB32340A928003D89EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 32 | BF1A1FBA2340A928003D89EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | BF1A1FBC2340A92B003D89EE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | BF1A1FBF2340A92B003D89EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35 | BF1A1FC12340A92B003D89EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | BF1A1FC82340A977003D89EE /* VHLNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VHLNavigation.swift; sourceTree = ""; }; 37 | BF3353E12340F07500D5201A /* BaseNavigationC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationC.swift; sourceTree = ""; }; 38 | BF3353E32340F08200D5201A /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 39 | BF555C7A235FE01300E624A9 /* BGViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGViewViewController.swift; sourceTree = ""; }; 40 | BFCF781623614EB2004A888C /* NavScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavScrollViewController.swift; sourceTree = ""; }; 41 | BFCF7818236153B4004A888C /* IgnoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoreViewController.swift; sourceTree = ""; }; 42 | BFF03FCF235ED31600A953A0 /* BGImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageViewController.swift; sourceTree = ""; }; 43 | BFF03FD1235ED33100A953A0 /* BaseTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTestViewController.swift; sourceTree = ""; }; 44 | BFF03FD3235ED79700A953A0 /* BGColorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGColorViewController.swift; sourceTree = ""; }; 45 | BFF03FD5235ED97600A953A0 /* BGAlphaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGAlphaViewController.swift; sourceTree = ""; }; 46 | BFF03FD7235EDABD00A953A0 /* BGHideViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGHideViewController.swift; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | BF1A1FAD2340A928003D89EE /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | BF1A1FA72340A928003D89EE = { 61 | isa = PBXGroup; 62 | children = ( 63 | BF1A1FB22340A928003D89EE /* VHLNavigation_Swift */, 64 | BF1A1FB12340A928003D89EE /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | BF1A1FB12340A928003D89EE /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | BF1A1FB02340A928003D89EE /* VHLNavigation_Swift.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | BF1A1FB22340A928003D89EE /* VHLNavigation_Swift */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | BF1A1FC72340A967003D89EE /* VHLNavigation */, 80 | BF3353E02340F03100D5201A /* Base */, 81 | BF1A1FB32340A928003D89EE /* AppDelegate.swift */, 82 | BFF03FD1235ED33100A953A0 /* BaseTestViewController.swift */, 83 | BF1752F523418DAB005741BF /* FakeViewController.swift */, 84 | BF555C7A235FE01300E624A9 /* BGViewViewController.swift */, 85 | BFF03FCF235ED31600A953A0 /* BGImageViewController.swift */, 86 | BFF03FD3235ED79700A953A0 /* BGColorViewController.swift */, 87 | BFF03FD5235ED97600A953A0 /* BGAlphaViewController.swift */, 88 | BFF03FD7235EDABD00A953A0 /* BGHideViewController.swift */, 89 | BFCF781623614EB2004A888C /* NavScrollViewController.swift */, 90 | BFCF7818236153B4004A888C /* IgnoreViewController.swift */, 91 | BF1A1FB92340A928003D89EE /* Main.storyboard */, 92 | BF1A1FBC2340A92B003D89EE /* Assets.xcassets */, 93 | BF1A1FBE2340A92B003D89EE /* LaunchScreen.storyboard */, 94 | BF1A1FC12340A92B003D89EE /* Info.plist */, 95 | ); 96 | path = VHLNavigation_Swift; 97 | sourceTree = ""; 98 | }; 99 | BF1A1FC72340A967003D89EE /* VHLNavigation */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | BF1A1FC82340A977003D89EE /* VHLNavigation.swift */, 103 | ); 104 | path = VHLNavigation; 105 | sourceTree = ""; 106 | }; 107 | BF3353E02340F03100D5201A /* Base */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | BF3353E12340F07500D5201A /* BaseNavigationC.swift */, 111 | BF3353E32340F08200D5201A /* BaseViewController.swift */, 112 | ); 113 | path = Base; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXNativeTarget section */ 119 | BF1A1FAF2340A928003D89EE /* VHLNavigation_Swift */ = { 120 | isa = PBXNativeTarget; 121 | buildConfigurationList = BF1A1FC42340A92B003D89EE /* Build configuration list for PBXNativeTarget "VHLNavigation_Swift" */; 122 | buildPhases = ( 123 | BF1A1FAC2340A928003D89EE /* Sources */, 124 | BF1A1FAD2340A928003D89EE /* Frameworks */, 125 | BF1A1FAE2340A928003D89EE /* Resources */, 126 | ); 127 | buildRules = ( 128 | ); 129 | dependencies = ( 130 | ); 131 | name = VHLNavigation_Swift; 132 | productName = VHLNavigation_Swift; 133 | productReference = BF1A1FB02340A928003D89EE /* VHLNavigation_Swift.app */; 134 | productType = "com.apple.product-type.application"; 135 | }; 136 | /* End PBXNativeTarget section */ 137 | 138 | /* Begin PBXProject section */ 139 | BF1A1FA82340A928003D89EE /* Project object */ = { 140 | isa = PBXProject; 141 | attributes = { 142 | LastSwiftUpdateCheck = 1100; 143 | LastUpgradeCheck = 1200; 144 | ORGANIZATIONNAME = "Darnel Studio"; 145 | TargetAttributes = { 146 | BF1A1FAF2340A928003D89EE = { 147 | CreatedOnToolsVersion = 11.0; 148 | }; 149 | }; 150 | }; 151 | buildConfigurationList = BF1A1FAB2340A928003D89EE /* Build configuration list for PBXProject "VHLNavigation_Swift" */; 152 | compatibilityVersion = "Xcode 9.3"; 153 | developmentRegion = en; 154 | hasScannedForEncodings = 0; 155 | knownRegions = ( 156 | en, 157 | Base, 158 | ); 159 | mainGroup = BF1A1FA72340A928003D89EE; 160 | productRefGroup = BF1A1FB12340A928003D89EE /* Products */; 161 | projectDirPath = ""; 162 | projectRoot = ""; 163 | targets = ( 164 | BF1A1FAF2340A928003D89EE /* VHLNavigation_Swift */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | BF1A1FAE2340A928003D89EE /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | BF1A1FC02340A92B003D89EE /* LaunchScreen.storyboard in Resources */, 175 | BF1A1FBD2340A92B003D89EE /* Assets.xcassets in Resources */, 176 | BF1A1FBB2340A928003D89EE /* Main.storyboard in Resources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXResourcesBuildPhase section */ 181 | 182 | /* Begin PBXSourcesBuildPhase section */ 183 | BF1A1FAC2340A928003D89EE /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | BF3353E42340F08200D5201A /* BaseViewController.swift in Sources */, 188 | BFF03FD2235ED33100A953A0 /* BaseTestViewController.swift in Sources */, 189 | BFF03FD0235ED31600A953A0 /* BGImageViewController.swift in Sources */, 190 | BF1A1FB42340A928003D89EE /* AppDelegate.swift in Sources */, 191 | BF1752F623418DAB005741BF /* FakeViewController.swift in Sources */, 192 | BFF03FD4235ED79700A953A0 /* BGColorViewController.swift in Sources */, 193 | BF555C7B235FE01300E624A9 /* BGViewViewController.swift in Sources */, 194 | BFF03FD6235ED97600A953A0 /* BGAlphaViewController.swift in Sources */, 195 | BF3353E22340F07500D5201A /* BaseNavigationC.swift in Sources */, 196 | BF1A1FC92340A977003D89EE /* VHLNavigation.swift in Sources */, 197 | BFF03FD8235EDABD00A953A0 /* BGHideViewController.swift in Sources */, 198 | BFCF781723614EB2004A888C /* NavScrollViewController.swift in Sources */, 199 | BFCF7819236153B4004A888C /* IgnoreViewController.swift in Sources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXSourcesBuildPhase section */ 204 | 205 | /* Begin PBXVariantGroup section */ 206 | BF1A1FB92340A928003D89EE /* Main.storyboard */ = { 207 | isa = PBXVariantGroup; 208 | children = ( 209 | BF1A1FBA2340A928003D89EE /* Base */, 210 | ); 211 | name = Main.storyboard; 212 | sourceTree = ""; 213 | }; 214 | BF1A1FBE2340A92B003D89EE /* LaunchScreen.storyboard */ = { 215 | isa = PBXVariantGroup; 216 | children = ( 217 | BF1A1FBF2340A92B003D89EE /* Base */, 218 | ); 219 | name = LaunchScreen.storyboard; 220 | sourceTree = ""; 221 | }; 222 | /* End PBXVariantGroup section */ 223 | 224 | /* Begin XCBuildConfiguration section */ 225 | BF1A1FC22340A92B003D89EE /* Debug */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | ALWAYS_SEARCH_USER_PATHS = NO; 229 | CLANG_ANALYZER_NONNULL = YES; 230 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 231 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 232 | CLANG_CXX_LIBRARY = "libc++"; 233 | CLANG_ENABLE_MODULES = YES; 234 | CLANG_ENABLE_OBJC_ARC = YES; 235 | CLANG_ENABLE_OBJC_WEAK = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 251 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 252 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 253 | CLANG_WARN_STRICT_PROTOTYPES = YES; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | COPY_PHASE_STRIP = NO; 259 | DEBUG_INFORMATION_FORMAT = dwarf; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | ENABLE_TESTABILITY = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu11; 263 | GCC_DYNAMIC_NO_PIC = NO; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_OPTIMIZATION_LEVEL = 0; 266 | GCC_PREPROCESSOR_DEFINITIONS = ( 267 | "DEBUG=1", 268 | "$(inherited)", 269 | ); 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 277 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 278 | MTL_FAST_MATH = YES; 279 | ONLY_ACTIVE_ARCH = YES; 280 | SDKROOT = iphoneos; 281 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 282 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 283 | }; 284 | name = Debug; 285 | }; 286 | BF1A1FC32340A92B003D89EE /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | CLANG_ANALYZER_NONNULL = YES; 291 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_ENABLE_OBJC_WEAK = YES; 297 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 298 | CLANG_WARN_BOOL_CONVERSION = YES; 299 | CLANG_WARN_COMMA = YES; 300 | CLANG_WARN_CONSTANT_CONVERSION = YES; 301 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 302 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 303 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 310 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 313 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 314 | CLANG_WARN_STRICT_PROTOTYPES = YES; 315 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 316 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 317 | CLANG_WARN_UNREACHABLE_CODE = YES; 318 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 319 | COPY_PHASE_STRIP = NO; 320 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 321 | ENABLE_NS_ASSERTIONS = NO; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | GCC_C_LANGUAGE_STANDARD = gnu11; 324 | GCC_NO_COMMON_BLOCKS = YES; 325 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 326 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 327 | GCC_WARN_UNDECLARED_SELECTOR = YES; 328 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 329 | GCC_WARN_UNUSED_FUNCTION = YES; 330 | GCC_WARN_UNUSED_VARIABLE = YES; 331 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 332 | MTL_ENABLE_DEBUG_INFO = NO; 333 | MTL_FAST_MATH = YES; 334 | SDKROOT = iphoneos; 335 | SWIFT_COMPILATION_MODE = wholemodule; 336 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 337 | VALIDATE_PRODUCT = YES; 338 | }; 339 | name = Release; 340 | }; 341 | BF1A1FC52340A92B003D89EE /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 345 | CODE_SIGN_STYLE = Automatic; 346 | DEVELOPMENT_TEAM = FAUZYDV99Z; 347 | INFOPLIST_FILE = VHLNavigation_Swift/Info.plist; 348 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 349 | LD_RUNPATH_SEARCH_PATHS = ( 350 | "$(inherited)", 351 | "@executable_path/Frameworks", 352 | ); 353 | MARKETING_VERSION = 1.0.2; 354 | PRODUCT_BUNDLE_IDENTIFIER = "cn.vincents.swift.VHLNavigation-Swift"; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | SWIFT_VERSION = 5.0; 357 | TARGETED_DEVICE_FAMILY = "1,2"; 358 | }; 359 | name = Debug; 360 | }; 361 | BF1A1FC62340A92B003D89EE /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 | CODE_SIGN_STYLE = Automatic; 366 | DEVELOPMENT_TEAM = FAUZYDV99Z; 367 | INFOPLIST_FILE = VHLNavigation_Swift/Info.plist; 368 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 369 | LD_RUNPATH_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "@executable_path/Frameworks", 372 | ); 373 | MARKETING_VERSION = 1.0.2; 374 | PRODUCT_BUNDLE_IDENTIFIER = "cn.vincents.swift.VHLNavigation-Swift"; 375 | PRODUCT_NAME = "$(TARGET_NAME)"; 376 | SWIFT_VERSION = 5.0; 377 | TARGETED_DEVICE_FAMILY = "1,2"; 378 | }; 379 | name = Release; 380 | }; 381 | /* End XCBuildConfiguration section */ 382 | 383 | /* Begin XCConfigurationList section */ 384 | BF1A1FAB2340A928003D89EE /* Build configuration list for PBXProject "VHLNavigation_Swift" */ = { 385 | isa = XCConfigurationList; 386 | buildConfigurations = ( 387 | BF1A1FC22340A92B003D89EE /* Debug */, 388 | BF1A1FC32340A92B003D89EE /* Release */, 389 | ); 390 | defaultConfigurationIsVisible = 0; 391 | defaultConfigurationName = Release; 392 | }; 393 | BF1A1FC42340A92B003D89EE /* Build configuration list for PBXNativeTarget "VHLNavigation_Swift" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | BF1A1FC52340A92B003D89EE /* Debug */, 397 | BF1A1FC62340A92B003D89EE /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | /* End XCConfigurationList section */ 403 | }; 404 | rootObject = BF1A1FA82340A928003D89EE /* Project object */; 405 | } 406 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift.xcodeproj/project.xcworkspace/xcuserdata/vincent.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/Demo/VHLNavigation_Swift.xcodeproj/project.xcworkspace/xcuserdata/vincent.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift.xcodeproj/xcuserdata/vincent.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift.xcodeproj/xcuserdata/vincent.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | VHLNavigation_Swift.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/9/29. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | 18 | window = UIWindow.init(frame: UIScreen.main.bounds) 19 | window?.backgroundColor = UIColor.white 20 | window?.makeKeyAndVisible() 21 | 22 | VHLNavigation.hook() 23 | VHLNavigation.def.navBarShadowImageHidden = true 24 | 25 | let vc = BaseTestViewController() 26 | let navVC = BaseNavigationC(rootViewController: vc) 27 | window?.rootViewController = navVC 28 | 29 | // Override point for customization after application launch. 30 | return true 31 | } 32 | 33 | // MARK: UISceneSession Lifecycle 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/head.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "head.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/head.imageset/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/Demo/VHLNavigation_Swift/Assets.xcassets/head.imageset/head.jpg -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/navbg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "millcolorGrad.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/navbg.imageset/millcolorGrad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/Demo/VHLNavigation_Swift/Assets.xcassets/navbg.imageset/millcolorGrad.png -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/vhl_nav_back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "vhl_nav_back@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "vhl_nav_back@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/vhl_nav_back.imageset/vhl_nav_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/Demo/VHLNavigation_Swift/Assets.xcassets/vhl_nav_back.imageset/vhl_nav_back@2x.png -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Assets.xcassets/vhl_nav_back.imageset/vhl_nav_back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/Demo/VHLNavigation_Swift/Assets.xcassets/vhl_nav_back.imageset/vhl_nav_back@3x.png -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/BGAlphaViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BGAlphaViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/22. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BGAlphaViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | self.title = "导航栏透明度" 18 | self.view.backgroundColor = .systemGreen 19 | 20 | self.vhl_navBarBackgroundAlpha = CGFloat(arc4random()) / CGFloat(UInt32.max) 21 | } 22 | // 状态栏样式 23 | override var preferredStatusBarStyle: UIStatusBarStyle { 24 | if #available(iOS 13.0, *) { 25 | return .darkContent 26 | } 27 | return .default 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/BGColorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BGColorViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/22. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BGColorViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "背景颜色" 17 | // 随机颜色 18 | self.vhl_navBarBackgroundColor = getRandomColor() 19 | self.vhl_navBarTintColor = getRandomColor() 20 | self.vhl_navBarTitleColor = getRandomColor() 21 | self.vhl_navBarShadowImageHide = false 22 | } 23 | 24 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 25 | super.touchesBegan(touches, with: event) 26 | self.vhl_navBarHide = !self.vhl_navBarHide 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/BGHideViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BGHideViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/22. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BGHideViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "隐藏导航栏" 17 | self.vhl_navBarHide = true 18 | // Do any additional setup after loading the view. 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/BGImageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BGImageViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/22. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BGImageViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "背景图片" 17 | 18 | let bgImage = UIImage(named: "navbg") 19 | self.vhl_navBarBackgroundImage = bgImage 20 | // self.vhl_navBarBackgroundColor = getRandomColor() 21 | self.vhl_navBarTintColor = getRandomColor() 22 | self.vhl_navBarTitleColor = getRandomColor() 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/BGViewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BGViewViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/23. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BGViewViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "自定义背景View(微信样式)" 17 | 18 | let blurBGColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:0.9) 19 | 20 | let viewBGView = UIView() //UIVisualEffectView(effect: blurEffect) 21 | viewBGView.frame = self.view.bounds 22 | viewBGView.backgroundColor = blurBGColor 23 | self.view.addSubview(viewBGView) 24 | self.view.sendSubviewToBack(viewBGView) 25 | 26 | // Do any additional setup after loading the view. 27 | let blurEffect = UIBlurEffect(style: .light) 28 | let bgView = UIVisualEffectView(effect: blurEffect) 29 | bgView.backgroundColor = blurBGColor 30 | // ** 给自定义的 View 标记tag, 如果两个 vc 的自定义view tag一样,那么不会以假导航栏样式过渡 31 | bgView.tag = 788 //Int(arc4random()) 32 | self.vhl_navBarBackgroundView = bgView 33 | self.vhl_navBarShadowImageHide = true 34 | 35 | // -------------------------------------------------------------------------- 36 | configSubviews() 37 | } 38 | func configSubviews() { 39 | for i in 0..<3 { 40 | let imageView = UIImageView(frame: CGRect(x: i*120, y: -10 * i, width: 120, height: 120)) 41 | imageView.image = UIImage(named: "head") 42 | self.view.addSubview(imageView) 43 | 44 | let testView = UIView(frame: CGRect(x: 40, y: 40, width: 40, height: 40)) 45 | testView.backgroundColor = getRandomColor() 46 | imageView.addSubview(testView) 47 | 48 | let blurEffect = UIBlurEffect(style: .light) 49 | let bgView = UIVisualEffectView(effect: blurEffect) 50 | bgView.frame = CGRect(x: 50, y: 50, width: 50, height: 50) 51 | imageView.addSubview(bgView) 52 | } 53 | } 54 | 55 | override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | } 58 | 59 | 60 | override var preferredStatusBarStyle: UIStatusBarStyle { 61 | return .lightContent 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Base/BaseNavigationC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNavigationC.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/9/29. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseNavigationC: UINavigationController, UIGestureRecognizerDelegate { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | self.interactivePopGestureRecognizer?.delegate = self 18 | } 19 | 20 | func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 21 | if self.viewControllers.count <= 1 { 22 | return false 23 | } 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Base/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/9/29. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.view.backgroundColor = .white 16 | // Do any additional setup after loading the view. 17 | } 18 | } 19 | extension BaseViewController { 20 | func getRandomColor() -> UIColor { 21 | return UIColor(red: CGFloat(arc4random()) / CGFloat(UInt32.max), 22 | green: CGFloat(arc4random()) / CGFloat(UInt32.max), 23 | blue: CGFloat(arc4random()) / CGFloat(UInt32.max), 24 | alpha: 1.0) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/BaseTestViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTestViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/22. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseTestViewController: BaseViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.view.backgroundColor = .white 16 | 17 | self.title = "VHLNavigation" 18 | configUI() 19 | // Do any additional setup after loading the view. 20 | } 21 | func configUI() { 22 | let startX = (self.view.frame.size.width - 200) / 2 23 | let startY: CGFloat = 72 24 | let itemHeight: CGFloat = 40 25 | let spacing: CGFloat = 12 26 | 27 | let button = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) + startY, width: 200, height: itemHeight)) 28 | button.setTitle("微信红包样式", for: .normal) 29 | button.setTitleColor(.white, for: .normal) 30 | button.backgroundColor = .black 31 | self.view.addSubview(button) 32 | 33 | let button1 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 2 + startY, width: 200, height: itemHeight)) 34 | button1.setTitle("微信动态模糊", for: .normal) 35 | button1.setTitleColor(.white, for: .normal) 36 | button1.backgroundColor = .black 37 | self.view.addSubview(button1) 38 | 39 | let button2 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 3 + startY, width: 200, height: itemHeight)) 40 | button2.setTitle("背景图片", for: .normal) 41 | button2.setTitleColor(.white, for: .normal) 42 | button2.backgroundColor = .black 43 | self.view.addSubview(button2) 44 | 45 | let button3 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 4 + startY, width: 200, height: itemHeight)) 46 | button3.setTitle("背景颜色", for: .normal) 47 | button3.setTitleColor(.white, for: .normal) 48 | button3.backgroundColor = .black 49 | self.view.addSubview(button3) 50 | 51 | let button4 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 5 + startY, width: 200, height: itemHeight)) 52 | button4.setTitle("背景透明度", for: .normal) 53 | button4.setTitleColor(.white, for: .normal) 54 | button4.backgroundColor = .black 55 | self.view.addSubview(button4) 56 | 57 | let button5 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 6 + startY, width: 200, height: itemHeight)) 58 | button5.setTitle("导航栏隐藏", for: .normal) 59 | button5.setTitleColor(.white, for: .normal) 60 | button5.backgroundColor = .black 61 | self.view.addSubview(button5) 62 | 63 | let button6 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 7 + startY, width: 200, height: itemHeight)) 64 | button6.setTitle("导航栏滚动", for: .normal) 65 | button6.setTitleColor(.white, for: .normal) 66 | button6.backgroundColor = .black 67 | self.view.addSubview(button6) 68 | 69 | let button7 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 8 + startY, width: 200, height: itemHeight)) 70 | button7.setTitle("模态跳转", for: .normal) 71 | button7.setTitleColor(.white, for: .normal) 72 | button7.backgroundColor = .black 73 | self.view.addSubview(button7) 74 | 75 | let button8 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 9 + startY, width: 200, height: itemHeight)) 76 | button8.setTitle("模态返回", for: .normal) 77 | button8.setTitleColor(.white, for: .normal) 78 | button8.backgroundColor = .black 79 | self.view.addSubview(button8) 80 | 81 | let button9 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 10 + startY, width: 200, height: itemHeight)) 82 | button9.setTitle("忽略的VC", for: .normal) 83 | button9.setTitleColor(.white, for: .normal) 84 | button9.backgroundColor = .black 85 | self.view.addSubview(button9) 86 | 87 | let button10 = UIButton(frame: CGRect(x: startX, y: (itemHeight + spacing) * 11 + startY, width: 200, height: itemHeight)) 88 | button10.setTitle("系统相册", for: .normal) 89 | button10.setTitleColor(.white, for: .normal) 90 | button10.backgroundColor = .black 91 | self.view.addSubview(button10) 92 | 93 | button.addTarget(self, action: #selector(wechatStyle), for: .touchUpInside) 94 | button1.addTarget(self, action: #selector(bgViewStyle), for: .touchUpInside) 95 | button2.addTarget(self, action: #selector(bgImageStyle), for: .touchUpInside) 96 | button3.addTarget(self, action: #selector(bgColorStyle), for: .touchUpInside) 97 | button4.addTarget(self, action: #selector(bgAlphaStyle), for: .touchUpInside) 98 | button5.addTarget(self, action: #selector(bgHideStyle), for: .touchUpInside) 99 | button6.addTarget(self, action: #selector(navScrollStyle), for: .touchUpInside) 100 | button7.addTarget(self, action: #selector(motalStyle), for: .touchUpInside) 101 | button8.addTarget(self, action: #selector(motalBack), for: .touchUpInside) 102 | button9.addTarget(self, action: #selector(ignoreVC), for: .touchUpInside) 103 | button10.addTarget(self, action: #selector(systemPhoto), for: .touchUpInside) 104 | } 105 | // MARK: actions 106 | @objc func wechatStyle() { 107 | let vc = FakeViewController() 108 | self.navigationController?.pushViewController(vc, animated: true) 109 | } 110 | @objc func bgViewStyle() { 111 | let vc = BGViewViewController() 112 | self.navigationController?.pushViewController(vc, animated: true) 113 | } 114 | @objc func bgImageStyle() { 115 | let vc = BGImageViewController() 116 | self.navigationController?.pushViewController(vc, animated: true) 117 | } 118 | @objc func bgColorStyle() { 119 | let vc = BGColorViewController() 120 | self.navigationController?.pushViewController(vc, animated: true) 121 | } 122 | @objc func bgAlphaStyle() { 123 | let vc = BGAlphaViewController() 124 | self.navigationController?.pushViewController(vc, animated: true) 125 | } 126 | @objc func bgHideStyle() { 127 | let vc = BGHideViewController() 128 | self.navigationController?.pushViewController(vc, animated: true) 129 | } 130 | @objc func navScrollStyle() { 131 | let vc = NavScrollViewController() 132 | self.navigationController?.pushViewController(vc, animated: true) 133 | } 134 | @objc func motalStyle() { 135 | let vc = BGViewViewController() 136 | let nav = BaseNavigationC(rootViewController: vc) 137 | self.present(nav, animated: true, completion: nil) 138 | } 139 | @objc func motalBack() { 140 | self.dismiss(animated: true, completion: nil) 141 | } 142 | @objc func ignoreVC() { 143 | VHLNavigation.def.addIgnoreVCName("IgnoreViewController") 144 | 145 | let vc = IgnoreViewController() 146 | self.navigationController?.pushViewController(vc, animated: true) 147 | } 148 | @objc func systemPhoto() { 149 | if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) { 150 | let imagePicker = UIImagePickerController() 151 | imagePicker.sourceType = .photoLibrary 152 | imagePicker.allowsEditing = true 153 | self.present(imagePicker, animated: true, completion: nil) 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/FakeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakeViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/9/30. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FakeViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "微信样式" 17 | 18 | self.vhl_navSwitchStyle = .fakeNavBar 19 | // 随机颜色 20 | self.vhl_navBarBackgroundColor = getRandomColor() 21 | self.vhl_navBarTintColor = getRandomColor() 22 | } 23 | 24 | override func viewWillAppear(_ animated: Bool) { 25 | super.viewWillAppear(animated) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/IgnoreViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IgnoreViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/24. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class IgnoreViewController: BaseTestViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "忽略的VC" 17 | } 18 | override func viewWillAppear(_ animated: Bool) { 19 | super.viewWillAppear(animated) 20 | self.navigationController?.navigationBar.tintColor = .red 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | 相册测试 25 | NSMicrophoneUsageDescription 26 | 相册测试 27 | NSPhotoLibraryUsageDescription 28 | 相册测试 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/NavScrollViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavScrollViewController.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/10/24. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NavScrollViewController: BaseViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.title = "导航栏滚动" 17 | self.vhl_navBarBackgroundColor = getRandomColor() 18 | 19 | // Do any additional setup after loading the view. 20 | let tableView = UITableView(frame: self.view.bounds) 21 | tableView.dataSource = self 22 | tableView.delegate = self 23 | self.view.addSubview(tableView) 24 | } 25 | } 26 | extension NavScrollViewController: UITableViewDataSource, UITableViewDelegate { 27 | func numberOfSections(in tableView: UITableView) -> Int { 28 | return 1 29 | } 30 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 31 | return 40 32 | } 33 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 34 | let cellIdentifier = "cell" 35 | var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) 36 | if cell == nil { 37 | cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier) 38 | } 39 | cell?.textLabel?.text = "\(indexPath.section) - \(indexPath.row)" 40 | cell?.textLabel?.textColor = getRandomColor() 41 | 42 | return cell! 43 | } 44 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 45 | tableView.deselectRow(at: indexPath, animated: true) 46 | 47 | let vc = FakeViewController() 48 | self.navigationController?.pushViewController(vc, animated: true) 49 | } 50 | } 51 | extension NavScrollViewController: UIScrollViewDelegate { 52 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 53 | var offsetY = scrollView.contentOffset.y 54 | if #available(iOS 11.0, *) { 55 | offsetY += scrollView.adjustedContentInset.top 56 | } 57 | self.vhl_navTranslationY = min(0, -offsetY) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Demo/VHLNavigation_Swift/VHLNavigation/VHLNavigation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VHLNavigation.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/9/29. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: 导航栏切换样式 12 | enum VHLNavigationSwitchStyle { 13 | case transition // 颜色过渡 14 | case fakeNavBar // 两种不同颜色导航栏,类似微信红包 15 | } 16 | // MARK: VHLNavigation 17 | class VHLNavigation: NSObject { 18 | static let def = VHLNavigation() 19 | 20 | var navBackgroundColor: UIColor = .white // 默认导航栏背景颜色 21 | var navBackgroundAlpha: CGFloat = 1.0 22 | var navBarTintColor: UIColor = .black // 默认导航栏按钮颜色 23 | var navBarTitleColor: UIColor = .black // 默认导航栏标题颜色 24 | var navBarShadowImageHidden: Bool = false // 默认导航栏分割线是否隐藏 25 | 26 | var ignoreVCList: Set = [] // 忽略的 viewControllers 列表 27 | 28 | override init() { 29 | 30 | } 31 | 32 | static func hook() { 33 | UINavigationBar.vhl_hookMethods() 34 | UIViewController.vhl_hookMethods() 35 | UINavigationController.vhl_navHookMethods() 36 | } 37 | } 38 | extension VHLNavigation { 39 | func isIgnoreVC(_ vcName: String) -> Bool { 40 | // 忽略系统类 41 | let systemClassPrefixs = ["_UI", "UI", "SFSafari", "MFMail", "PUPhoto", "CKSMS", "MPMedia"] 42 | for prefix in systemClassPrefixs { 43 | if vcName.hasPrefix(prefix) { 44 | return true 45 | } 46 | } 47 | 48 | return self.ignoreVCList.contains(vcName) 49 | } 50 | func addIgnoreVCName(_ vcName: String) { 51 | self.ignoreVCList.insert(vcName) 52 | } 53 | func removeIgnoreVCName(_ vcName: String) { 54 | self.ignoreVCList.remove(vcName) 55 | } 56 | func removeAllIgnoreVCName() { 57 | self.ignoreVCList.removeAll() 58 | } 59 | } 60 | extension VHLNavigation { 61 | // MARK: 是否是 iPhone X 系列的异形屏 62 | static func isIPhoneXSeries() -> Bool { 63 | // 不是 iPhone 64 | if UIDevice.current.userInterfaceIdiom != .phone { 65 | return false 66 | } 67 | if #available(iOS 11.0, *) { 68 | if let mainWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) { 69 | if mainWindow.safeAreaInsets.top > 0 { 70 | return true 71 | } 72 | } 73 | } 74 | 75 | return false 76 | } 77 | // MARK: fromColor toColor 过渡的颜色 78 | static func middleColor(from fromColor: UIColor, to toColor: UIColor, percent: CGFloat) -> UIColor { 79 | // get current color RGBA 80 | var fromRed: CGFloat = 0 81 | var fromGreen: CGFloat = 0 82 | var fromBlue: CGFloat = 0 83 | var fromAlpha: CGFloat = 0 84 | fromColor.getRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha) 85 | 86 | // get to color RGBA 87 | var toRed: CGFloat = 0 88 | var toGreen: CGFloat = 0 89 | var toBlue: CGFloat = 0 90 | var toAlpha: CGFloat = 0 91 | toColor.getRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha) 92 | 93 | // calculate middle color RGBA 94 | let newRed = fromRed + (toRed - fromRed) * percent 95 | let newGreen = fromGreen + (toGreen - fromGreen) * percent 96 | let newBlue = fromBlue + (toBlue - fromBlue) * percent 97 | let newAlpha = fromAlpha + (toAlpha - fromAlpha) * percent 98 | return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha) 99 | } 100 | // MARK: middle alpha 101 | static func middleAlpha(from fromAlpha: CGFloat, to toAlpha: CGFloat, percent: CGFloat) -> CGFloat { 102 | let newAlpha = fromAlpha + (toAlpha - fromAlpha) * percent 103 | return newAlpha 104 | } 105 | } 106 | // MARK: - UINavigationBar 相关修改 107 | extension UINavigationBar { 108 | // MARK: property 109 | fileprivate struct AssociatedKeys { 110 | static var backgroundView: UIView = UIView() 111 | static var backgroundImageView: UIImageView = UIImageView() 112 | } 113 | 114 | fileprivate var backgroundView: UIView? { 115 | get { 116 | guard let bgView = objc_getAssociatedObject(self, &AssociatedKeys.backgroundView) as? UIView else { 117 | return nil 118 | } 119 | return bgView 120 | } 121 | set { 122 | objc_setAssociatedObject(self, &AssociatedKeys.backgroundView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 123 | } 124 | } 125 | 126 | fileprivate var backgroundImageView: UIImageView? { 127 | get { 128 | guard let bgImageView = objc_getAssociatedObject(self, &AssociatedKeys.backgroundImageView) as? UIImageView else { 129 | return nil 130 | } 131 | return bgImageView 132 | } 133 | set { 134 | objc_setAssociatedObject(self, &AssociatedKeys.backgroundImageView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 135 | } 136 | } 137 | // MARK: 设置导航栏自定义背景View 138 | public func vhl_setBackgroundView(_ view: UIView) { 139 | backgroundImageView?.removeFromSuperview() 140 | backgroundImageView = nil 141 | 142 | backgroundView?.removeFromSuperview() 143 | 144 | /// 这里需要将系统添加的模糊层隐藏,不然会在自己添加的背景层再添加一层模糊层 145 | if self.subviews.first?.subviews.count ?? 0 > 1 { 146 | if let backgroundEffectView = self.subviews.first?.subviews[1] { 147 | backgroundEffectView.alpha = 0.0 148 | } 149 | } 150 | 151 | if let viewCopyData = try? NSKeyedArchiver.archivedData(withRootObject: view, requiringSecureCoding: false) { 152 | // if let viewCopy = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIView.self, from: viewCopyData) { 153 | if let viewCopy = NSKeyedUnarchiver.unarchiveObject(with: viewCopyData) as? UIView { 154 | self.backgroundView = viewCopy 155 | if let backgroundView = self.backgroundView { 156 | backgroundView.frame = self.subviews.first!.bounds 157 | backgroundView.autoresizingMask = [.flexibleWidth, 158 | .flexibleHeight, 159 | .flexibleBottomMargin] 160 | /// iOS 11 下导航栏不显示问题 161 | if self.subviews.count > 0 { 162 | self.subviews.first?.insertSubview(backgroundView, at: 0) 163 | } else { 164 | self.insertSubview(backgroundView, at: 0) 165 | } 166 | } 167 | } 168 | } 169 | } 170 | // MARK: 设置导航栏背景图片 171 | public func vhl_setBackgroundImage(_ image: UIImage) { 172 | backgroundView?.removeFromSuperview() 173 | backgroundView = nil 174 | 175 | if backgroundImageView == nil { 176 | // add a image(nil color) to _UIBarBackground make it clear 177 | setBackgroundImage(UIImage(), for: .default) 178 | if let superView = self.subviews.first { 179 | self.backgroundImageView = UIImageView(frame: superView.bounds) 180 | if let backgroundImageView = self.backgroundImageView { 181 | backgroundImageView.tag = 10102 182 | backgroundImageView.autoresizingMask = [.flexibleWidth, 183 | .flexibleHeight, 184 | .flexibleBottomMargin] 185 | superView.insertSubview(backgroundImageView, at: 0) 186 | } 187 | } 188 | } 189 | self.backgroundImageView?.image = image 190 | } 191 | // MARK: 设置导航栏背景颜色 192 | public func vhl_setBackgroundColor(_ color: UIColor) { 193 | backgroundImageView?.removeFromSuperview() 194 | backgroundImageView = nil 195 | 196 | /// 这里自定义 bgView 也是使用的同一个变量 197 | let bgViewTag = 10101 198 | if backgroundView?.tag ?? 0 != bgViewTag { 199 | backgroundView?.removeFromSuperview() 200 | backgroundView = nil 201 | } 202 | 203 | if backgroundView == nil { 204 | if let superView = self.subviews.first { 205 | // add a image(nil color) to _UIBarBackground make it clear 206 | setBackgroundImage(UIImage(), for: .default) 207 | self.backgroundView = UIView(frame: superView.bounds) 208 | self.backgroundView?.tag = bgViewTag 209 | if let backgroundView = self.backgroundView { 210 | backgroundView.autoresizingMask = [.flexibleWidth, 211 | .flexibleHeight, 212 | .flexibleBottomMargin] 213 | superView.insertSubview(backgroundView, at: 0) 214 | } 215 | } 216 | } 217 | self.backgroundView?.backgroundColor = color 218 | } 219 | // MARK: 设置导航栏背景透明度 220 | public func vhl_setBackgroundAlpha(_ alpha: CGFloat) { 221 | // set _UIBarBackground alpha 222 | if let barBackgroundView = self.subviews.first { 223 | if #available(iOS 11.0, *) { // sometimes we can't change _UIBarBackground alpha 224 | for view in barBackgroundView.subviews { 225 | view.alpha = alpha 226 | } 227 | } 228 | barBackgroundView.alpha = alpha 229 | } 230 | } 231 | // MARK: 设置导航栏所有 barButtonItem 的透明度 232 | public func vhl_setBarButtonItemsAlpha(alpha: CGFloat, hasSystemBackIndicator:Bool) { 233 | for view in self.subviews { 234 | if hasSystemBackIndicator { 235 | if let _UIBarBackgroundClass = NSClassFromString("_UIBarBackground") { 236 | if !view.isKind(of: _UIBarBackgroundClass) { 237 | view.alpha = alpha 238 | } 239 | } 240 | if let _UINavigationBarBackground = NSClassFromString("_UINavigationBarBackground") { 241 | if !view.isKind(of: _UINavigationBarBackground) { 242 | view.alpha = alpha 243 | } 244 | } 245 | } else { 246 | // 这里如果不做判断的话,会显示 backIndicatorImage(系统返回按钮) 247 | if let _UINavigationBarBackIndicatorViewClass = NSClassFromString("_UINavigationBarBackIndicatorView"), 248 | !view.isKind(of: _UINavigationBarBackIndicatorViewClass) { 249 | if let _UIBarBackgroundClass = NSClassFromString("_UIBarBackground") { 250 | if view.isKind(of: _UIBarBackgroundClass) { 251 | view.alpha = alpha 252 | } 253 | } 254 | 255 | if let _UINavigationBarBackground = NSClassFromString("_UINavigationBarBackground") { 256 | if !view.isKind(of: _UINavigationBarBackground) { 257 | view.alpha = alpha 258 | } 259 | } 260 | } 261 | } 262 | } 263 | } 264 | // MARK: 设置默认的返回箭头是否隐藏 265 | public func vhl_setBarBackIndicatorViewIsHidden(_ isHidden: Bool) { 266 | for view in self.subviews { 267 | if let _UINavigationBarBackIndicatorViewClass = NSClassFromString("_UINavigationBarBackIndicatorView") { 268 | if view.isKind(of: _UINavigationBarBackIndicatorViewClass) { 269 | view.isHidden = isHidden 270 | } 271 | } 272 | } 273 | } 274 | // MARK: 分割线是否隐藏 275 | public func vhl_setShadowImageIsHidden(_ isHidden: Bool) { 276 | self.shadowImage = isHidden ? UIImage() : nil 277 | // iOS 11 后设置 shadowImage 无效 278 | self.setValue(isHidden, forKey: "hidesShadow") 279 | self.layoutIfNeeded() 280 | } 281 | // MARK: 设置/获取导航栏偏移量 282 | public func vhl_setTranslationY(y: CGFloat) { 283 | self.transform = CGAffineTransform(translationX: 0, y: y) 284 | } 285 | public func vhl_translationY() -> CGFloat { 286 | return self.transform.ty 287 | } 288 | 289 | // MARK: Hook methods 290 | private static let onceToken = UUID().uuidString 291 | static fileprivate func vhl_hookMethods() { 292 | DispatchQueue.vhl_once(token: onceToken) { 293 | let needSwizzleSelectorArr = [ 294 | #selector(setter: titleTextAttributes) 295 | ] 296 | 297 | for selector in needSwizzleSelectorArr { 298 | let str = ("vhl_" + selector.description) 299 | if let originalMethod = class_getInstanceMethod(self, selector), 300 | let swizzledMethod = class_getInstanceMethod(self, Selector(str)) { 301 | method_exchangeImplementations(originalMethod, swizzledMethod) 302 | } 303 | } 304 | } 305 | } 306 | @objc func vhl_setTitleTextAttributes(_ newTitleTextAttributes:[NSAttributedString.Key : Any]?) { 307 | guard var attributes = newTitleTextAttributes else { return } 308 | 309 | guard let originTitleTextAttributes = titleTextAttributes else { 310 | vhl_setTitleTextAttributes(attributes) 311 | return 312 | } 313 | 314 | var titleColor:UIColor? 315 | for attribute in originTitleTextAttributes { 316 | if attribute.key == NSAttributedString.Key.foregroundColor { 317 | titleColor = attribute.value as? UIColor 318 | break 319 | } 320 | } 321 | 322 | guard let originTitleColor = titleColor else { 323 | vhl_setTitleTextAttributes(attributes) 324 | return 325 | } 326 | 327 | if attributes[NSAttributedString.Key.foregroundColor] == nil { 328 | attributes.updateValue(originTitleColor, forKey: NSAttributedString.Key.foregroundColor) 329 | } 330 | 331 | // crash: 'Have you sent -vhl_setTitleTextAttributes: 332 | /// ** VHLNavigation.hook() 需要将在 viewController 初始化的最前面调用 ** 333 | vhl_setTitleTextAttributes(attributes) 334 | } 335 | } 336 | // MARK: - Hook UINavigationController 337 | extension UINavigationController: UINavigationBarDelegate { 338 | // MARK: 状态栏相关 339 | open override var preferredStatusBarStyle: UIStatusBarStyle { 340 | return topViewController?.preferredStatusBarStyle ?? UIStatusBarStyle.default 341 | } 342 | // MARK: 更新导航栏相关样式 343 | fileprivate func setNeedsNavigationBarUpdate(backgroundView: UIView) { // 导航栏背景视图 344 | navigationBar.vhl_setBackgroundView(backgroundView) 345 | } 346 | fileprivate func setNeedsNavigationBarUpdate(backgroundImage: UIImage) { // 导航栏背景图片 347 | navigationBar.vhl_setBackgroundImage(backgroundImage) 348 | } 349 | fileprivate func setNeedsNavigationBarUpdate(backgroundColor: UIColor) { // 导航栏背景颜色 350 | navigationBar.vhl_setBackgroundColor(backgroundColor) 351 | } 352 | fileprivate func setNeedsNavigationBarUpdate(backgroundAlpha: CGFloat) { // 导航栏背景透明度 353 | navigationBar.vhl_setBackgroundAlpha(backgroundAlpha) 354 | } 355 | fileprivate func setNeedsNavigationBarUpdate(tintColor: UIColor) { // 导航栏按钮颜色 356 | navigationBar.tintColor = tintColor 357 | } 358 | fileprivate func setNeedsNavigationBarUpdate(titleColor: UIColor) { // 导航栏标题颜色 359 | guard let titleTextAttributes = navigationBar.titleTextAttributes else { 360 | navigationBar.titleTextAttributes = [.foregroundColor: titleColor] 361 | return 362 | } 363 | var newTitleTextAttributes = titleTextAttributes 364 | newTitleTextAttributes.updateValue(titleColor, forKey: .foregroundColor) 365 | navigationBar.titleTextAttributes = newTitleTextAttributes 366 | } 367 | fileprivate func setNeedsNavigationBarUpdate(hidenShadowImage: Bool) { // 导航栏分割线隐藏 368 | navigationBar.vhl_setShadowImageIsHidden(hidenShadowImage) 369 | } 370 | 371 | // MARK: - Hook methods 372 | private static let onceToken = UUID().uuidString 373 | static fileprivate func vhl_navHookMethods() { // NavigationController 是继承自 vc 的 374 | DispatchQueue.vhl_once(token: onceToken) { 375 | let needSwizzleSelectorArr = [ 376 | NSSelectorFromString("_updateInteractiveTransition:"), 377 | #selector(popToViewController(_:animated:)), 378 | #selector(popToRootViewController(animated:)), 379 | #selector(pushViewController(_:animated:)), 380 | ] 381 | 382 | for selector in needSwizzleSelectorArr { 383 | // _updateInteractiveTransition: => vhl_updateInteractiveTransition: 384 | let str = ("vhl_" + selector.description).replacingOccurrences(of: "__", with: "_") 385 | if let originalMethod = class_getInstanceMethod(self, selector), 386 | let swizzledMethod = class_getInstanceMethod(self, Selector(str)) { 387 | method_exchangeImplementations(originalMethod, swizzledMethod) 388 | } 389 | } 390 | } 391 | } 392 | // MARK: hook popVC 393 | fileprivate struct VHLPOPProperties { 394 | static var popDuration = 0.13 // 侧滑动画时间 395 | static var displayCount = 0 // 396 | static var popProgress: CGFloat { // pop 进度 397 | let all:CGFloat = CGFloat(60.0 * popDuration) 398 | let current = min(all, CGFloat(displayCount)) 399 | return current / all 400 | } 401 | } 402 | @objc func vhl_popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { 403 | let displayLink = CADisplayLink(target: self, selector: #selector(popNeedDisplay)) 404 | displayLink.add(to: RunLoop.main, forMode: .common) 405 | CATransaction.setCompletionBlock { 406 | displayLink.invalidate() 407 | VHLPOPProperties.displayCount = 0 408 | } 409 | CATransaction.setAnimationDuration(VHLPOPProperties.popDuration) 410 | CATransaction.begin() 411 | let vcs = vhl_popToViewController(viewController, animated: animated) // 调用自己 412 | CATransaction.commit() 413 | return vcs 414 | } 415 | @objc func vhl_popToRootViewControllerAnimated(_ animated: Bool) -> [UIViewController]? { 416 | var displayLink: CADisplayLink? = CADisplayLink(target: self, selector: #selector(popNeedDisplay)) 417 | displayLink?.add(to: RunLoop.main, forMode: .common) 418 | CATransaction.setCompletionBlock { 419 | displayLink?.invalidate() 420 | displayLink = nil 421 | VHLPOPProperties.displayCount = 0 422 | } 423 | CATransaction.setAnimationDuration(VHLPOPProperties.popDuration) 424 | CATransaction.begin() 425 | let vcs = vhl_popToRootViewControllerAnimated(animated) // 调用自己 426 | CATransaction.commit() 427 | return vcs 428 | } 429 | // MARK: DisplayLink 侧滑监听 430 | @objc fileprivate func popNeedDisplay() { 431 | guard let topViewController = self.topViewController, let coordinator = topViewController.transitionCoordinator else { return } 432 | 433 | VHLPOPProperties.displayCount += 1 434 | let popProgress = VHLPOPProperties.popProgress 435 | let fromVC = coordinator.viewController(forKey: .from) 436 | let toVC = coordinator.viewController(forKey: .to) 437 | updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: popProgress) 438 | } 439 | // MARK: hook push vc 440 | fileprivate struct VHLPushProperties { 441 | static var pushDuration = 0.13 // 侧滑动画时间 442 | static var displayCount = 0 // 443 | static var pushProgress: CGFloat { // pop 进度 444 | let all:CGFloat = CGFloat(60.0 * pushDuration) 445 | let current = min(all, CGFloat(displayCount)) 446 | return current / all 447 | } 448 | } 449 | @objc func vhl_pushViewController(_ viewController: UIViewController, animated: Bool) { 450 | var displayLink: CADisplayLink? = CADisplayLink(target: self, selector: #selector(pushNeedDisplay)) 451 | displayLink?.add(to: RunLoop.main, forMode: .common) 452 | CATransaction.setCompletionBlock { 453 | displayLink?.invalidate() 454 | displayLink = nil 455 | VHLPushProperties.displayCount = 0 456 | viewController.pushToCurrentVCFinished = true 457 | } 458 | CATransaction.setAnimationDuration(VHLPushProperties.pushDuration) 459 | CATransaction.begin() 460 | vhl_pushViewController(viewController, animated: animated) // 调用自己 461 | CATransaction.commit() 462 | } 463 | @objc fileprivate func pushNeedDisplay() { 464 | guard let topViewController = self.topViewController, let coordinator = topViewController.transitionCoordinator else { return } 465 | if topViewController.isMotalFrom() { return } 466 | 467 | VHLPushProperties.displayCount += 1 468 | let pushProgress = VHLPushProperties.pushProgress 469 | let fromVC = coordinator.viewController(forKey: .from) 470 | let toVC = coordinator.viewController(forKey: .to) 471 | updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: pushProgress) 472 | } 473 | // MARK: hook 导航栏侧滑进度 474 | @objc func vhl_updateInteractiveTransition(_ percentComplete: CGFloat) { 475 | guard let topViewController = self.topViewController, let coordinator = topViewController.transitionCoordinator else { 476 | vhl_updateInteractiveTransition(percentComplete) 477 | return 478 | } 479 | let fromVC = coordinator.viewController(forKey: .from) 480 | let toVC = coordinator.viewController(forKey: .to) 481 | updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: percentComplete) 482 | // 调用自己 483 | vhl_updateInteractiveTransition(percentComplete) 484 | } 485 | 486 | // MARK: 根据进度更新导航栏 487 | fileprivate func updateNavigationBar(fromVC: UIViewController?, toVC: UIViewController?, progress: CGFloat) { 488 | // 背景颜色变化 489 | if let fromVC = fromVC, let toVC = toVC { 490 | // 是否是被忽略的 VC 491 | if fromVC.isIgnoreVC() || toVC.isIgnoreVC() { return } 492 | // 是否需要添加假的导航栏 493 | if self.topViewController?.shouldAddFakeNavigationBar() ?? false { return } 494 | 495 | // 颜色过渡 496 | // 1. 导航栏按钮颜色 497 | let fromTintColor = fromVC.vhl_navBarTintColor 498 | let toTintColor = toVC.vhl_navBarTintColor 499 | let newTintColor = VHLNavigation.middleColor(from: fromTintColor, to: toTintColor, percent: progress) 500 | self.setNeedsNavigationBarUpdate(tintColor: newTintColor) 501 | 502 | // 2. 导航栏标题颜色 503 | let fromTitleColor = fromVC.vhl_navBarTitleColor 504 | let toTitleColor = toVC.vhl_navBarTitleColor 505 | let newTitleColor = VHLNavigation.middleColor(from: fromTitleColor, to: toTitleColor, percent: progress) 506 | self.setNeedsNavigationBarUpdate(titleColor: newTitleColor) 507 | 508 | /// 背景颜色过渡时,判断是否有相同的自定义背景 View 509 | if fromVC.vhl_navBarBackgroundView != nil && 510 | self.topViewController?.fromToVCBackgroundViewIsSame() ?? false { return } 511 | 512 | // 3. 导航栏背景颜色 513 | let fromBackgroundColor = fromVC.vhl_navBarBackgroundColor 514 | let toBackgroundColor = toVC.vhl_navBarBackgroundColor 515 | let newBackgroundColor = VHLNavigation.middleColor(from: fromBackgroundColor, to: toBackgroundColor, percent: progress) 516 | self.setNeedsNavigationBarUpdate(backgroundColor: newBackgroundColor) 517 | 518 | // 4. 导航栏透明度 519 | let fromAlpha = fromVC.vhl_navBarBackgroundAlpha 520 | let toAlpha = toVC.vhl_navBarBackgroundAlpha 521 | let newAlpha = VHLNavigation.middleAlpha(from: fromAlpha, to: toAlpha, percent: progress) 522 | self.setNeedsNavigationBarUpdate(backgroundAlpha: newAlpha) 523 | } 524 | } 525 | // MARK: Delegate - UINavigationBarDelegate 526 | public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { 527 | if let topVC = self.topViewController, let coor = topVC.transitionCoordinator, coor.initiallyInteractive { 528 | if #available(iOS 10.0, *) { 529 | coor.notifyWhenInteractionChanges { (context) in 530 | self.dealInteractionChanges(context) 531 | } 532 | } else { 533 | coor.notifyWhenInteractionEnds { (context) in 534 | self.dealInteractionChanges(context) 535 | } 536 | } 537 | } 538 | let itemCount = navigationBar.items?.count ?? 0 539 | let n = self.viewControllers.count >= itemCount ? 2 : 1 540 | let popToVC = self.viewControllers[viewControllers.count - n] 541 | popToViewController(popToVC, animated: true) 542 | // fix: iOS 13 下点击导航栏返回按钮闪退 543 | if #available(iOS 13.0, *) { 544 | return false 545 | } 546 | return true 547 | } 548 | // deal the gesture of return break off 549 | private func dealInteractionChanges(_ context: UIViewControllerTransitionCoordinatorContext) { 550 | let animations: (UITransitionContextViewControllerKey) -> () = { 551 | if !(self.topViewController?.shouldAddFakeNavigationBar() ?? false) { 552 | let curBackgroundColor = context.viewController(forKey: $0)?.vhl_navBarBackgroundColor ?? VHLNavigation.def.navBackgroundColor 553 | let curBackgroundAlpha = context.viewController(forKey: $0)?.vhl_navBarBackgroundAlpha ?? VHLNavigation.def.navBackgroundAlpha 554 | 555 | self.setNeedsNavigationBarUpdate(backgroundColor: curBackgroundColor) 556 | self.setNeedsNavigationBarUpdate(backgroundAlpha: curBackgroundAlpha) 557 | } 558 | } 559 | if context.isCancelled { 560 | let cancelDuration = context.transitionDuration * Double(context.percentComplete) 561 | UIView.animate(withDuration: cancelDuration) { 562 | animations(.from) 563 | } 564 | } else { 565 | let finishDuration = context.transitionDuration * Double(1 - context.percentComplete) 566 | UIView.animate(withDuration: finishDuration) { 567 | animations(.to) 568 | } 569 | } 570 | } 571 | } 572 | extension UINavigationController { 573 | // 1. 是否支持自动转屏 574 | open override var shouldAutorotate: Bool { 575 | return self.topViewController?.shouldAutorotate ?? false 576 | } 577 | // 2. 支持哪些屏幕方向 578 | open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 579 | return self.topViewController?.supportedInterfaceOrientations ?? .portrait 580 | } 581 | // 4. 默认的屏幕方向(当前 ViewController 必须是通过模态出来的 UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 582 | open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 583 | return self.topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait 584 | } 585 | 586 | } 587 | // MARK: - Hook ViewController 588 | extension UIViewController { 589 | fileprivate struct AssociatedKeys { 590 | // 跳转到当前是否已完成 591 | static var pushToCurrentVCFinished: Bool = false 592 | // 跳转到下一个 VC 是否已完成 593 | static var pushToNextVCFinished: Bool = false 594 | 595 | // 当前导航栏切换样式 596 | static var navSwitchStyle: VHLNavigationSwitchStyle = .transition 597 | // 当前导航栏是否隐藏 598 | static var navBarHide: Bool = false 599 | // 当前导航栏背景视图 600 | static var navBarBackgroundView: UIView? 601 | // 当前导航栏背景图片 602 | static var navBarBackgroundImage: UIImage? 603 | // 当前导航栏背景颜色 604 | static var navBarBackgroundColor: UIColor = VHLNavigation.def.navBackgroundColor 605 | // 当前导航栏透明度 606 | static var navBarBackgroundAlpha: CGFloat = 1.0 607 | // 当前导航栏按钮颜色 608 | static var navBarTintColor: UIColor = VHLNavigation.def.navBarTintColor 609 | // 当前导航栏标题颜色 610 | static var navBarTitleColor: UIColor = VHLNavigation.def.navBarTitleColor 611 | // 当前导航栏底部分割线是否隐藏 612 | static var navBarShadowImageHide: Bool = VHLNavigation.def.navBarShadowImageHidden 613 | // 当前导航栏浮动高度 614 | static var navBarTranslationY: CGFloat = 0.0 615 | // 当前状态栏样式 616 | static var statusBarStyle: UIStatusBarStyle = .default 617 | // 当前侧滑手势是否可用 618 | static var interactivePopEnable: Bool = true 619 | 620 | // 假的导航栏 621 | static var fakeNavBar: UIImageView = UIImageView() 622 | static var tempBackView: UIView? 623 | } 624 | // MARK: property 625 | fileprivate var pushToCurrentVCFinished: Bool { 626 | get { 627 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.pushToCurrentVCFinished) as? Bool else { 628 | return false 629 | } 630 | return isFinished 631 | } 632 | set { 633 | objc_setAssociatedObject(self, &AssociatedKeys.pushToCurrentVCFinished, newValue, .OBJC_ASSOCIATION_ASSIGN) 634 | } 635 | } 636 | fileprivate var pushToNextVCFinished: Bool { 637 | get { 638 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.pushToNextVCFinished) as? Bool else { 639 | return false 640 | } 641 | return isFinished 642 | } 643 | set { 644 | objc_setAssociatedObject(self, &AssociatedKeys.pushToNextVCFinished, newValue, .OBJC_ASSOCIATION_ASSIGN) 645 | } 646 | } 647 | fileprivate var fakeNavBar: UIImageView? { 648 | get { 649 | return objc_getAssociatedObject(self, &AssociatedKeys.fakeNavBar) as? UIImageView 650 | } 651 | set { 652 | objc_setAssociatedObject(self, &AssociatedKeys.fakeNavBar, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 653 | } 654 | } 655 | fileprivate var tempBackView: UIView? { 656 | get { 657 | return objc_getAssociatedObject(self, &AssociatedKeys.tempBackView) as? UIView 658 | } 659 | set { 660 | objc_setAssociatedObject(self, &AssociatedKeys.tempBackView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 661 | } 662 | } 663 | // MARK: 公开属性 **** 664 | /// 当前导航栏切换样式 665 | var vhl_navSwitchStyle: VHLNavigationSwitchStyle { 666 | get { 667 | guard let style = objc_getAssociatedObject(self, &AssociatedKeys.navSwitchStyle) as? VHLNavigationSwitchStyle else { 668 | return .transition 669 | } 670 | return style 671 | } 672 | set { 673 | objc_setAssociatedObject(self, &AssociatedKeys.navSwitchStyle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 674 | } 675 | } 676 | /// 当前导航栏是否隐藏 677 | var vhl_navBarHide: Bool { 678 | get { 679 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.navBarHide) as? Bool else { 680 | return false 681 | } 682 | return isFinished 683 | } 684 | set { 685 | objc_setAssociatedObject(self, &AssociatedKeys.navBarHide, newValue, .OBJC_ASSOCIATION_ASSIGN) 686 | if pushToCurrentVCFinished { 687 | navigationController?.setNavigationBarHidden(newValue, animated: true) 688 | } 689 | } 690 | } 691 | /// 当前导航栏自定义背景 view 692 | var vhl_navBarBackgroundView: UIView? { 693 | get { 694 | let bgView = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundView) as? UIView 695 | return bgView 696 | } 697 | set { 698 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 699 | if pushToNextVCFinished == false { 700 | if let bgView = newValue { 701 | navigationController?.setNeedsNavigationBarUpdate(backgroundView: bgView) 702 | } 703 | } 704 | } 705 | } 706 | /// 当前导航栏背景图片 707 | var vhl_navBarBackgroundImage: UIImage? { 708 | get { 709 | let bgImage = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundImage) as? UIImage 710 | return bgImage 711 | } 712 | set { 713 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundImage, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 714 | if pushToNextVCFinished == false { 715 | if let bgImage = newValue { 716 | navigationController?.setNeedsNavigationBarUpdate(backgroundImage: bgImage) 717 | } 718 | } 719 | } 720 | } 721 | /// 当前导航栏背景颜色 722 | var vhl_navBarBackgroundColor: UIColor { 723 | get { 724 | guard let bgColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundColor) as? UIColor else { 725 | return VHLNavigation.def.navBackgroundColor 726 | } 727 | return bgColor 728 | } 729 | set { 730 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 731 | if pushToNextVCFinished == false { 732 | navigationController?.setNeedsNavigationBarUpdate(backgroundColor: newValue) 733 | } 734 | } 735 | } 736 | /// 当前导航栏背景透明度 737 | var vhl_navBarBackgroundAlpha: CGFloat { 738 | get { 739 | guard let bgColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundAlpha) as? CGFloat else { 740 | return 1.0 741 | } 742 | return bgColor 743 | } 744 | set { 745 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundAlpha, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 746 | if pushToNextVCFinished == false { 747 | navigationController?.setNeedsNavigationBarUpdate(backgroundAlpha: newValue) 748 | } 749 | } 750 | } 751 | /// 当前导航栏按钮颜色 752 | var vhl_navBarTintColor: UIColor { 753 | get { 754 | guard let tintColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarTintColor) as? UIColor else { 755 | return VHLNavigation.def.navBarTintColor 756 | } 757 | return tintColor 758 | } 759 | set { 760 | objc_setAssociatedObject(self, &AssociatedKeys.navBarTintColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 761 | 762 | if pushToNextVCFinished == false { 763 | navigationController?.setNeedsNavigationBarUpdate(tintColor: newValue) 764 | } 765 | } 766 | } 767 | /// 当前导航栏标题颜色 768 | var vhl_navBarTitleColor: UIColor { 769 | get { 770 | guard let tintColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarTitleColor) as? UIColor else { 771 | return VHLNavigation.def.navBarTitleColor 772 | } 773 | return tintColor 774 | } 775 | set { 776 | objc_setAssociatedObject(self, &AssociatedKeys.navBarTitleColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 777 | 778 | if pushToNextVCFinished == false { 779 | navigationController?.setNeedsNavigationBarUpdate(titleColor: newValue) 780 | } 781 | } 782 | } 783 | /// 当前导航栏分割线是否隐藏 784 | var vhl_navBarShadowImageHide: Bool { 785 | get { 786 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.navBarShadowImageHide) as? Bool else { 787 | return VHLNavigation.def.navBarShadowImageHidden 788 | } 789 | return isFinished 790 | } 791 | set { 792 | objc_setAssociatedObject(self, &AssociatedKeys.navBarShadowImageHide, newValue, .OBJC_ASSOCIATION_ASSIGN) 793 | if pushToNextVCFinished == false { 794 | self.navigationController?.setNeedsNavigationBarUpdate(hidenShadowImage: newValue) 795 | } 796 | } 797 | } 798 | /// 当前状态栏样式 799 | var vhl_statusBarStyle: UIStatusBarStyle { 800 | get { 801 | guard let style = objc_getAssociatedObject(self, &AssociatedKeys.statusBarStyle) as? UIStatusBarStyle else { 802 | return .default 803 | } 804 | return style 805 | } 806 | set { 807 | objc_setAssociatedObject(self, &AssociatedKeys.statusBarStyle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 808 | setNeedsStatusBarAppearanceUpdate() 809 | } 810 | } 811 | /// 当前侧滑手势是否可用 812 | var vhl_interactivePopEnable: Bool { 813 | get { 814 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.interactivePopEnable) as? Bool else { 815 | return true 816 | } 817 | return isFinished 818 | } 819 | set { 820 | objc_setAssociatedObject(self, &AssociatedKeys.interactivePopEnable, newValue, .OBJC_ASSOCIATION_ASSIGN) 821 | updateInteractivePopGestureRecognizer() 822 | } 823 | } 824 | /// 当前导航栏偏移 825 | var vhl_navTranslationY: CGFloat { 826 | get { 827 | let transY = objc_getAssociatedObject(self, &AssociatedKeys.navBarTranslationY) as? CGFloat ?? 0.0 828 | return transY 829 | } 830 | set { 831 | objc_setAssociatedObject(self, &AssociatedKeys.navBarTranslationY, newValue, .OBJC_ASSOCIATION_ASSIGN) 832 | self.navigationController?.navigationBar.vhl_setTranslationY(y: newValue) 833 | } 834 | } 835 | // MARK: - Hook methods 836 | private static let onceToken = UUID().uuidString 837 | static fileprivate func vhl_hookMethods() { 838 | DispatchQueue.vhl_once(token: onceToken) { 839 | let needSwizzleSelectors = [ 840 | #selector(viewWillAppear(_:)), 841 | #selector(viewWillDisappear(_:)), 842 | #selector(viewDidAppear(_:)), 843 | #selector(viewDidDisappear(_:)) 844 | ] 845 | // 交换方法 846 | for selector in needSwizzleSelectors{ 847 | let newSelectorStr = "vhl_" + selector.description 848 | if let originalMethod = class_getInstanceMethod(self, selector), 849 | let swizzledMethod = class_getInstanceMethod(self, Selector(newSelectorStr)) { 850 | method_exchangeImplementations(originalMethod, swizzledMethod) 851 | } 852 | } 853 | } 854 | } 855 | @objc private func vhl_viewWillAppear(_ animated: Bool) { 856 | if self.canUpdateNavigationBar() && !self.isIgnoreVC() { 857 | self.pushToNextVCFinished = false 858 | self.setNeedsStatusBarAppearanceUpdate() // 更新状态栏 859 | if let navVC = self.navigationController { 860 | // 导航栏返回按钮 861 | if navVC.viewControllers.count == 1 { 862 | navVC.navigationBar.vhl_setBarBackIndicatorViewIsHidden(true) 863 | } else { 864 | navVC.navigationBar.vhl_setBarBackIndicatorViewIsHidden(false) 865 | } 866 | // 当前导航栏是否隐藏 867 | navVC.setNavigationBarHidden(self.vhl_navBarHide, animated: animated) 868 | // 恢复导航栏偏移 869 | self.vhl_navTranslationY = 0.0 // 修复导航栏偏移 870 | // 添加一个假的导航栏 871 | if self.shouldAddFakeNavigationBar() { 872 | self.addFakeNavigationBar() 873 | } 874 | // 更新导航栏信息 875 | if !self.vhl_navBarHide && !self.isIgnoreVC() { 876 | let isModal = (self.isMotalFrom() || self.isMotalTo()) 877 | 878 | // ** 当两个VC都是颜色过渡的时候,这里不设置背景,不然会闪动一下 ** 879 | // ** 模态跳转下,需要更新导航背景,不然有概率出现白色背景 880 | if self.fakeNavBar != nil && (!self.isNavTransition() || self.isRootViewController()) || isModal { 881 | self.updateNavigationBackground() 882 | } 883 | navVC.setNeedsNavigationBarUpdate(tintColor: self.vhl_navBarTintColor) 884 | navVC.setNeedsNavigationBarUpdate(titleColor: self.vhl_navBarTitleColor) 885 | } 886 | } 887 | } 888 | // 调用自己 889 | vhl_viewWillAppear(animated) 890 | } 891 | @objc private func vhl_viewDidAppear(_ animated: Bool) { 892 | if !self.isRootViewController() { self.pushToCurrentVCFinished = true } 893 | self.removeFakeNavigationBar() // 移除假的导航栏 894 | 895 | if self.canUpdateNavigationBar() { 896 | if let navVC = self.navigationController { 897 | if self.isIgnoreVC() { // 如果是忽略的 vc 898 | navVC.setNeedsNavigationBarUpdate(backgroundAlpha: 1.0) 899 | navVC.setNeedsNavigationBarUpdate(tintColor: navVC.navigationBar.tintColor) 900 | navVC.setNeedsNavigationBarUpdate(backgroundImage: UIImage()) 901 | if let barTintColor = navVC.navigationBar.barTintColor { 902 | navVC.setNeedsNavigationBarUpdate(backgroundColor: barTintColor) 903 | } 904 | } else { 905 | self.updateNavigationAllInfo() 906 | } 907 | } 908 | } 909 | self.updateInteractivePopGestureRecognizer() 910 | // 调用自己 911 | vhl_viewDidAppear(animated) 912 | } 913 | @objc private func vhl_viewWillDisappear(_ animated: Bool) { 914 | if self.canUpdateNavigationBar() && !self.isIgnoreVC() && !self.isMotalTo() { 915 | // 取消隐藏导航栏 916 | self.navigationController?.setNavigationBarHidden(self.vhl_navBarHide, animated: animated) // self.vhl_navBarHide 917 | self.pushToNextVCFinished = true 918 | } 919 | // 调用自己 920 | vhl_viewWillDisappear(animated) 921 | } 922 | @objc private func vhl_viewDidDisappear(_ animated: Bool) { 923 | if self.canUpdateNavigationBar() { 924 | // 移除假的导航栏 925 | self.removeFakeNavigationBar() 926 | // 恢复导航栏浮动 927 | self.vhl_navTranslationY = 0 928 | } 929 | // 调用自己 930 | vhl_viewDidDisappear(animated) 931 | } 932 | } 933 | fileprivate extension UIViewController { 934 | // MARK: 判断当前是否能更新导航栏 935 | func canUpdateNavigationBar() -> Bool { 936 | guard let navigationController = self.navigationController else { 937 | return false 938 | } 939 | return navigationController.viewControllers.contains(self) 940 | } 941 | // MARK: 更新导航栏背景(背景视图/背景图片/背景颜色) 942 | func updateNavigationBackground() { 943 | if let navVC = self.navigationController { 944 | if let bgView = self.vhl_navBarBackgroundView { 945 | navVC.setNeedsNavigationBarUpdate(backgroundView: bgView) 946 | } else if let bgImage = self.vhl_navBarBackgroundImage { 947 | navVC.setNeedsNavigationBarUpdate(backgroundImage: bgImage) 948 | } else { 949 | navVC.setNeedsNavigationBarUpdate(backgroundColor: self.vhl_navBarBackgroundColor) 950 | } 951 | } 952 | } 953 | // MARK: 更新导航栏的所有属性信息 954 | func updateNavigationAllInfo() { 955 | // 背景 956 | updateNavigationBackground() 957 | // 导航栏按钮/标题/透明度/分割线 958 | if let navVC = self.navigationController { 959 | navVC.setNeedsNavigationBarUpdate(tintColor: self.vhl_navBarTintColor) 960 | navVC.setNeedsNavigationBarUpdate(titleColor: self.vhl_navBarTitleColor) 961 | navVC.setNeedsNavigationBarUpdate(backgroundAlpha: self.vhl_navBarBackgroundAlpha) 962 | navVC.setNeedsNavigationBarUpdate(hidenShadowImage: self.vhl_navBarShadowImageHide) 963 | } 964 | } 965 | // MARK: 更新侧滑手势开启关闭 966 | func updateInteractivePopGestureRecognizer() { 967 | if let navVC = self.navigationController { 968 | if let panRecognizer = navVC.interactivePopGestureRecognizer { 969 | if navVC.viewControllers.count <= 1 { 970 | panRecognizer.isEnabled = false 971 | } else { 972 | panRecognizer.isEnabled = self.vhl_interactivePopEnable 973 | } 974 | } 975 | } 976 | } 977 | } 978 | fileprivate extension UIViewController { 979 | // MARK: 导航栏是否是颜色过渡 980 | func isNavTransition() -> Bool { 981 | guard let fromVC = self.fromVC() else { return false } 982 | guard let toVC = self.toVC() else { return false } 983 | // 984 | if fromVC.vhl_navBarHide || toVC.vhl_navBarHide { return false } 985 | if fromVC.vhl_navBarBackgroundView != nil || toVC.vhl_navBarBackgroundView != nil { return false } 986 | if fromVC.vhl_navBarBackgroundImage != nil || toVC.vhl_navBarBackgroundImage != nil { return false } 987 | if fromVC.vhl_navSwitchStyle == .fakeNavBar || toVC.vhl_navSwitchStyle == .fakeNavBar { return false } 988 | if fromVC.vhl_navBarBackgroundAlpha != toVC.vhl_navBarBackgroundAlpha { return false } 989 | 990 | return true 991 | } 992 | // MARK: 是否是被忽略的 viewController 993 | func isIgnoreVC() -> Bool { 994 | if let selfClassName = NSStringFromClass(self.classForCoder).components(separatedBy: ".").last { 995 | return VHLNavigation.def.isIgnoreVC(selfClassName) 996 | } 997 | return false 998 | } 999 | } 1000 | extension UIViewController { 1001 | // 判断当前是否是第一个视图 1002 | func isRootViewController() -> Bool { 1003 | if self.navigationController?.viewControllers.count ?? 0 > 1 { return false } 1004 | 1005 | if let rootVC = self.navigationController?.viewControllers.first { 1006 | if rootVC.isKind(of: UITabBarController.self) { 1007 | if let tabbarVC = rootVC as? UITabBarController { 1008 | return tabbarVC.viewControllers?.contains(self) ?? false 1009 | } 1010 | } else { 1011 | return rootVC == self 1012 | } 1013 | } 1014 | return false 1015 | } 1016 | /// 当前是否进行了模态跳转 1017 | func isMotalTo() -> Bool { 1018 | if self.presentedViewController != nil { return true } 1019 | return false 1020 | } 1021 | /// 当前是否是模态跳转而来 1022 | func isMotalFrom() -> Bool { 1023 | if let viewControllers = self.navigationController?.viewControllers { 1024 | if viewControllers.count > 1 { 1025 | if viewControllers.last! == self { 1026 | return false 1027 | } 1028 | } 1029 | } 1030 | if self.presentingViewController != nil { return true } 1031 | return false 1032 | } 1033 | 1034 | // MARK: from VC 1035 | func fromVC() -> UIViewController? { 1036 | return self.navigationController?.topViewController?.transitionCoordinator?.viewController(forKey: .from) 1037 | } 1038 | // MARK: to VC 1039 | func toVC() -> UIViewController? { 1040 | return self.navigationController?.topViewController?.transitionCoordinator?.viewController(forKey: .to) 1041 | } 1042 | // MARK: 导航栏高度 1043 | func statusBarHeight() -> CGFloat { 1044 | return UIApplication.shared.statusBarFrame.height 1045 | } 1046 | func navigationBarHeight() -> CGFloat { 1047 | return self.navigationController?.navigationBar.bounds.height ?? 0.0 1048 | } 1049 | func navigationBarAndStatusBarHeight() -> CGFloat { 1050 | let navHeight = self.navigationBarHeight() 1051 | var statusHeight = self.statusBarHeight() 1052 | // 分享热点,拨打电话等,导航栏变低 1053 | if !VHLNavigation.isIPhoneXSeries() { 1054 | statusHeight = min(20, statusHeight) 1055 | } 1056 | 1057 | return navHeight + statusHeight 1058 | } 1059 | } 1060 | // MARK: 假导航栏操作 1061 | fileprivate extension UIViewController { 1062 | // MARK: 切换的导航栏自定义 View 是否一样 1063 | func fromToVCBackgroundViewIsSame() -> Bool { 1064 | guard let fromVC = self.fromVC() else { return false } 1065 | guard let toVC = self.toVC() else { return false } 1066 | 1067 | if (fromVC.vhl_navBarBackgroundView == nil) && (toVC.vhl_navBarBackgroundView == nil) { return true } 1068 | if (fromVC.vhl_navBarBackgroundView == nil) != (toVC.vhl_navBarBackgroundView == nil) { return false } 1069 | if let fromBGView = fromVC.vhl_navBarBackgroundView, let toBGView = toVC.vhl_navBarBackgroundView { 1070 | if fromBGView.classForCoder != toBGView.classForCoder { return false } 1071 | if fromBGView.tag == 0 || fromBGView.tag != toBGView.tag { return false } 1072 | 1073 | return true 1074 | } 1075 | return false 1076 | } 1077 | // MARK: 判断当前是否需要添加一个假的导航栏 1078 | func shouldAddFakeNavigationBar() -> Bool { 1079 | guard let fromVC = self.fromVC() else { return false } 1080 | guard let toVC = self.toVC() else { return false } 1081 | if fromVC.isIgnoreVC() || toVC.isIgnoreVC() { return false } 1082 | 1083 | if fromVC.vhl_navSwitchStyle == .fakeNavBar || toVC.vhl_navSwitchStyle == .fakeNavBar { return true } 1084 | if fromVC.vhl_navBarBackgroundImage != nil || toVC.vhl_navBarBackgroundImage != nil { return true } 1085 | if fromVC.vhl_navBarHide != toVC.vhl_navBarHide { return true } 1086 | if fromVC.vhl_navBarBackgroundAlpha != toVC.vhl_navBarBackgroundAlpha { return true } 1087 | // 自定义背景 View。如果自定义 view 类型一样,且 tag 也一样,不执行假导航栏过渡 1088 | if !self.fromToVCBackgroundViewIsSame() { return true } 1089 | 1090 | return false 1091 | } 1092 | // MARK: 添加一个假的导航栏 1093 | func addFakeNavigationBar() { 1094 | guard let fromVC = self.fromVC() else { return } 1095 | guard let toVC = self.toVC() else { return } 1096 | 1097 | fromVC.removeFakeNavigationBar() 1098 | toVC.removeFakeNavigationBar() 1099 | 1100 | if fromVC.isIgnoreVC() && toVC.isIgnoreVC() { return } 1101 | 1102 | let isTranslucent = self.navigationController?.navigationBar.isTranslucent ?? true 1103 | 1104 | if !fromVC.vhl_navBarHide { 1105 | // 添加假导航栏 1106 | var fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarAndStatusBarHeight()) 1107 | if #available(iOS 13.0, *) { // iOS 13 模态跳转 1108 | if let navVC = self.navigationController { 1109 | if fromVC.isMotalFrom() && navVC.modalPresentationStyle == .pageSheet { 1110 | fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarHeight()) 1111 | } 1112 | } 1113 | } 1114 | // 2. 判断当前 vc 是否是 UITableViewController 或 UICollectionViewController , 因为这种 vc.view 会为 scrollview 1115 | // ** 虽然 view frame 为全屏开始,但是因为安全区域,使得内容视图在导航栏下面 ** 1116 | if fromVC.view.isKind(of: UIScrollView.self) || 1117 | fromVC.edgesForExtendedLayout == UIRectEdge.init(rawValue: 0) || 1118 | fromVC.view.bounds.height < fromVC.navigationController?.view.bounds.height ?? 0.0 { 1119 | fakeNavFrame = fromVC.view.convert(fakeNavFrame, from: fromVC.navigationController?.view) 1120 | } 1121 | // 1122 | fromVC.fakeNavBar = UIImageView(frame: fakeNavFrame) 1123 | if let fakeNavBar = fromVC.fakeNavBar { 1124 | if let bgView = fromVC.vhl_navBarBackgroundView { 1125 | fakeNavBar.backgroundColor = .clear 1126 | 1127 | /// ** 这里需要对 view 进行拷贝,避免 UINavigationBar 中对 bgView 移除操作 1128 | if let viewCopyData = try? NSKeyedArchiver.archivedData(withRootObject: bgView, requiringSecureCoding: false) { 1129 | // if let viewCopy = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIView.self, from: bgViewCopyData) { 1130 | if let viewCopy = NSKeyedUnarchiver.unarchiveObject(with: viewCopyData) as? UIView { 1131 | viewCopy.frame = fakeNavFrame 1132 | fakeNavBar.addSubview(viewCopy) 1133 | } 1134 | } 1135 | } else { 1136 | fakeNavBar.backgroundColor = fromVC.vhl_navBarBackgroundColor 1137 | if #available(iOS 13.0, *) { // iOS 13 下导航栏透明度问题 1138 | /// 非 .clear 的情况 1139 | if fromVC.vhl_navBarBackgroundColor != UIColor.clear { 1140 | fakeNavBar.backgroundColor = fromVC.vhl_navBarBackgroundColor.withAlphaComponent(fromVC.vhl_navBarBackgroundAlpha) 1141 | } 1142 | } 1143 | fakeNavBar.image = fromVC.vhl_navBarBackgroundImage 1144 | // 如果是忽略的VC,且默认的背景颜色有值 1145 | if let barTintColor = fromVC.navigationController?.navigationBar.barTintColor { 1146 | if fromVC.isIgnoreVC() { 1147 | fakeNavBar.backgroundColor = barTintColor 1148 | } 1149 | } 1150 | } 1151 | 1152 | fakeNavBar.alpha = fromVC.vhl_navBarBackgroundAlpha // 导航栏透明度 1153 | fromVC.view.addSubview(fakeNavBar) 1154 | fromVC.view.bringSubviewToFront(fakeNavBar) 1155 | // 隐藏系统导航栏背景 1156 | fromVC.navigationController?.setNeedsNavigationBarUpdate(backgroundAlpha: 0.0) 1157 | 1158 | // - 当从有状态栏切换到无状态栏时,会出现一个当前 vc 显示了底部 vc 的内容,这里增加一个 view 用于遮盖 1159 | var tempFakeNavBarFrame = fakeNavFrame 1160 | tempFakeNavBarFrame.size.height = tempFakeNavBarFrame.height + 20.0 1161 | fromVC.tempBackView = UIView(frame: tempFakeNavBarFrame) 1162 | if let tempBackView = fromVC.tempBackView { 1163 | tempBackView.backgroundColor = fromVC.view.backgroundColor 1164 | fromVC.view.addSubview(tempBackView) 1165 | fromVC.view.sendSubviewToBack(tempBackView) 1166 | } 1167 | } 1168 | } 1169 | 1170 | if !toVC.vhl_navBarHide { 1171 | // 添加假导航栏 1172 | var fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarAndStatusBarHeight()) 1173 | 1174 | if #available(iOS 13.0, *) { // iOS 13 模态跳转 1175 | if let navVC = self.navigationController { 1176 | if fromVC.isMotalFrom() && navVC.modalPresentationStyle == .pageSheet { 1177 | fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarHeight()) 1178 | } 1179 | } 1180 | } 1181 | 1182 | // 当前 fromVC 隐藏导航栏,且 isTranslucent = false 时。toVC 的 fakeNav 位置会不正确 1183 | if fromVC.vhl_navBarHide && !isTranslucent { 1184 | fakeNavFrame.origin.y = -fakeNavFrame.height 1185 | } 1186 | 1187 | // 2. 判断当前 vc 是否是 UITableViewController 或 UICollectionViewController , 因为这种 vc.view 会为 scrollview 1188 | // ** 虽然 view frame 为全屏开始,但是因为安全区域,使得内容视图在导航栏下面 ** 1189 | if toVC.view.isKind(of: UIScrollView.self) || 1190 | toVC.edgesForExtendedLayout == UIRectEdge.init(rawValue: 0) || 1191 | toVC.view.bounds.height < toVC.navigationController?.view.bounds.height ?? 0.0 { 1192 | fakeNavFrame = toVC.view.convert(fakeNavFrame, from: toVC.navigationController?.view) 1193 | } 1194 | // 1195 | toVC.fakeNavBar = UIImageView(frame: fakeNavFrame) 1196 | if let fakeNavBar = toVC.fakeNavBar { 1197 | if let bgView = toVC.vhl_navBarBackgroundView { 1198 | fakeNavBar.backgroundColor = .clear 1199 | 1200 | if let viewCopyData = try? NSKeyedArchiver.archivedData(withRootObject: bgView, requiringSecureCoding: false) { 1201 | // if let bgViewCopy = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIView.self, from: bgViewCopyData) { 1202 | if let viewCopy = NSKeyedUnarchiver.unarchiveObject(with: viewCopyData) as? UIView { 1203 | viewCopy.frame = fakeNavFrame 1204 | fakeNavBar.addSubview(viewCopy) 1205 | } 1206 | } 1207 | } else { 1208 | fakeNavBar.backgroundColor = toVC.vhl_navBarBackgroundColor 1209 | if #available(iOS 13.0, *) { // iOS 13 下导航栏透明度问题 1210 | /// 非 .clear 的情况 1211 | if toVC.vhl_navBarBackgroundColor != UIColor.clear { 1212 | fakeNavBar.backgroundColor = toVC.vhl_navBarBackgroundColor.withAlphaComponent(toVC.vhl_navBarBackgroundAlpha) 1213 | } 1214 | } 1215 | fakeNavBar.image = toVC.vhl_navBarBackgroundImage 1216 | // 如果是忽略的VC,且默认的背景颜色有值 1217 | if let barTintColor = toVC.navigationController?.navigationBar.barTintColor { 1218 | if toVC.isIgnoreVC() { 1219 | fakeNavBar.backgroundColor = barTintColor 1220 | } 1221 | } 1222 | } 1223 | fakeNavBar.alpha = toVC.vhl_navBarBackgroundAlpha // 导航栏透明度 1224 | toVC.view.addSubview(fakeNavBar) 1225 | toVC.view.bringSubviewToFront(fakeNavBar) 1226 | // 隐藏系统导航栏背景 1227 | toVC.navigationController?.setNeedsNavigationBarUpdate(backgroundAlpha: 0.0) 1228 | } 1229 | } 1230 | } 1231 | // 移除假的导航栏 1232 | func removeFakeNavigationBar() { 1233 | if let fakeNavBar = self.fakeNavBar { 1234 | fakeNavBar.removeFromSuperview() 1235 | self.fakeNavBar = nil 1236 | } 1237 | if let tempBackView = self.tempBackView { 1238 | tempBackView.removeFromSuperview() 1239 | self.tempBackView = nil 1240 | } 1241 | } 1242 | } 1243 | // MARK: - DispatchQueue 单例执行 1244 | fileprivate extension DispatchQueue { 1245 | private static var onceTracker = [String]() 1246 | //Executes a block of code, associated with a unique token, only once. The code is thread safe and will only execute the code once even in the presence of multithreaded calls. 1247 | static func vhl_once(token: String, block: () -> Void) { // 保证被 objc_sync_enter 和 objc_sync_exit 包裹的代码可以有序同步地执行 1248 | objc_sync_enter(self) 1249 | defer { // 作用域结束后执行defer中的代码 1250 | objc_sync_exit(self) 1251 | } 1252 | 1253 | if onceTracker.contains(token) { 1254 | return 1255 | } 1256 | 1257 | onceTracker.append(token) 1258 | block() 1259 | } 1260 | } 1261 | // MARK: - UIApplication 程序第一次运行时注入运行时注入 1262 | // 这里需要自己手动调用进行注入 1263 | extension UIApplication { 1264 | /// ** VHLNavigation.hook() 需要将在 viewController 初始化的最前面调用 ** 1265 | static func VHLNavigationHook() { 1266 | VHLNavigation.hook() 1267 | } 1268 | static let VHLNavigation_runOnce: Void = { // 使用静态变量。用于只调用一次 1269 | VHLNavigation.hook() 1270 | }() 1271 | // iOS 13.4 下因为有 UIScene 会不生效 1272 | // open override var next: UIResponder? { 1273 | // UIApplication.VHLNavigation_runOnce 1274 | // return super.next 1275 | // } 1276 | } 1277 | 1278 | /** 1279 | 注意: 1280 | 1. 隐藏导航栏后,侧滑手势需要自己实现一个 UINavigationController 导航栏基类,不然侧滑手势不可用 1281 | self.interactivePopGestureRecognizer?.delegate = self 1282 | func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 1283 | if self.viewControllers.count <= 1 { 1284 | return false 1285 | } 1286 | return true 1287 | } 1288 | 1289 | 2. 1290 | */ 1291 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VHLNavigation 2 | 3 | 微信动态模糊样式,微信红包两种导航栏样式切换,颜色过渡切换,导航栏背景图片切换,导航栏透明度切换,有无导航栏切换. 4 | 5 | OC 版 [VHLNavigation](https://github.com/huanglins/VHLNavigation) 6 | 7 | ![微信动态模糊](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/自定义View.gif) 8 | ![微信样式](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/微信样式.gif) 9 | ![颜色过渡](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/颜色过渡.gif) 10 | ![背景图片](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/背景图片.gif) 11 | ![隐藏导航栏](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/隐藏导航栏.gif) 12 | ![导航栏透明度](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/透明度.gif) 13 | ![导航栏滚动](https://github.com/huanglins/VHLNavigation_Swift/raw/master/screenshots/导航栏滚动.gif) 14 | 15 | 参考学习 16 | 17 | [透明与半透明 NavigationBar 切换的三种方案](http://www.jianshu.com/p/e3ca1b7b6cec) 18 | 19 | [HansNavController](https://github.com/CrazyGitter/HansNavController) 20 | 21 | [WRNavigationBar](https://github.com/wangrui460/WRNavigationBar) 22 | 23 | # 如何使用 24 | 手动拖入 将 `VHLNavigation` 文件夹拽入项目中 25 | 26 | 或者通过 pod 导入 `pod 'VHLNavigation_Swift'` 27 | 28 | 导入头文件:`#import "VHLNavigation.h"` 29 | 30 | 31 | #### 相关用法 32 | 用法和 `VHLNavigation` OC 版一致 33 | 34 | ``` 35 | 设置导航栏背景图片 36 | self.vhl_navBarBackgroundImage = UIImage(named: "navbg") 37 | 设置导航栏背景颜色 38 | self.vhl_navBarBackgroundColor = getRandomColor() 39 | 设置导航栏透明度 40 | self.vhl_navBarBackgroundAlpha = CGFloat(arc4random()) / CGFloat(UInt32.max) 41 | 设置导航栏标题颜色 42 | self.vhl_navBarTitleColor = getRandomColor() 43 | 设置导航栏按钮颜色 44 | self.vhl_navBarTintColor = getRandomColor() 45 | 设置是否隐藏分割线 46 | self.vhl_navBarShadowImageHide = false 47 | 48 | 隐藏导航栏 49 | self.vhl_navBarHide = true 50 | ``` 51 | #### 设置为微信红包样式切换 52 | ``` 53 | self.vhl_navSwitchStyle = .fakeNavBar 54 | ``` 55 | 56 | #### 设置自定义的View (微信动态模糊样式) 57 | ``` 58 | let blurBGColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:0.9) 59 | 60 | let blurEffect = UIBlurEffect(style: .light) 61 | let bgView = UIVisualEffectView(effect: blurEffect) 62 | bgView.backgroundColor = blurBGColor 63 | 64 | // ** 给自定义的 View 标记tag, 如果两个 vc 的自定义view tag一样,那么不会以假导航栏样式过渡 65 | bgView.tag = 788 //Int(arc4random()) 66 | 67 | self.vhl_navBarBackgroundView = bgView 68 | ``` 69 | 70 | ## 更新 71 | 72 | 73 | # 关于 74 | - **blog**: https://www.vincents.cn 75 | - **email**: gvincent@163.com 76 | - **qq**: 2801818138 77 | 78 | 79 | -------------------------------------------------------------------------------- /VHLNavigation/VHLNavigation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VHLNavigation.swift 3 | // VHLNavigation_Swift 4 | // 5 | // Created by Vincent on 2019/9/29. 6 | // Copyright © 2019 Darnel Studio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: 导航栏切换样式 12 | enum VHLNavigationSwitchStyle { 13 | case transition // 颜色过渡 14 | case fakeNavBar // 两种不同颜色导航栏,类似微信红包 15 | } 16 | // MARK: VHLNavigation 17 | class VHLNavigation: NSObject { 18 | static let def = VHLNavigation() 19 | 20 | var navBackgroundColor: UIColor = .white // 默认导航栏背景颜色 21 | var navBackgroundAlpha: CGFloat = 1.0 22 | var navBarTintColor: UIColor = .black // 默认导航栏按钮颜色 23 | var navBarTitleColor: UIColor = .black // 默认导航栏标题颜色 24 | var navBarShadowImageHidden: Bool = false // 默认导航栏分割线是否隐藏 25 | 26 | var ignoreVCList: Set = [] // 忽略的 viewControllers 列表 27 | 28 | override init() { 29 | 30 | } 31 | 32 | static func hook() { 33 | UINavigationBar.vhl_hookMethods() 34 | UIViewController.vhl_hookMethods() 35 | UINavigationController.vhl_navHookMethods() 36 | } 37 | } 38 | extension VHLNavigation { 39 | func isIgnoreVC(_ vcName: String) -> Bool { 40 | // 忽略系统类 41 | let systemClassPrefixs = ["_UI", "UI", "SFSafari", "MFMail", "PUPhoto", "CKSMS", "MPMedia"] 42 | for prefix in systemClassPrefixs { 43 | if vcName.hasPrefix(prefix) { 44 | return true 45 | } 46 | } 47 | 48 | return self.ignoreVCList.contains(vcName) 49 | } 50 | func addIgnoreVCName(_ vcName: String) { 51 | self.ignoreVCList.insert(vcName) 52 | } 53 | func removeIgnoreVCName(_ vcName: String) { 54 | self.ignoreVCList.remove(vcName) 55 | } 56 | func removeAllIgnoreVCName() { 57 | self.ignoreVCList.removeAll() 58 | } 59 | } 60 | extension VHLNavigation { 61 | // MARK: 是否是 iPhone X 系列的异形屏 62 | static func isIPhoneXSeries() -> Bool { 63 | // 不是 iPhone 64 | if UIDevice.current.userInterfaceIdiom != .phone { 65 | return false 66 | } 67 | if #available(iOS 11.0, *) { 68 | if let mainWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) { 69 | if mainWindow.safeAreaInsets.top > 0 { 70 | return true 71 | } 72 | } 73 | } 74 | 75 | return false 76 | } 77 | // MARK: fromColor toColor 过渡的颜色 78 | static func middleColor(from fromColor: UIColor, to toColor: UIColor, percent: CGFloat) -> UIColor { 79 | // get current color RGBA 80 | var fromRed: CGFloat = 0 81 | var fromGreen: CGFloat = 0 82 | var fromBlue: CGFloat = 0 83 | var fromAlpha: CGFloat = 0 84 | fromColor.getRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha) 85 | 86 | // get to color RGBA 87 | var toRed: CGFloat = 0 88 | var toGreen: CGFloat = 0 89 | var toBlue: CGFloat = 0 90 | var toAlpha: CGFloat = 0 91 | toColor.getRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha) 92 | 93 | // calculate middle color RGBA 94 | let newRed = fromRed + (toRed - fromRed) * percent 95 | let newGreen = fromGreen + (toGreen - fromGreen) * percent 96 | let newBlue = fromBlue + (toBlue - fromBlue) * percent 97 | let newAlpha = fromAlpha + (toAlpha - fromAlpha) * percent 98 | return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha) 99 | } 100 | // MARK: middle alpha 101 | static func middleAlpha(from fromAlpha: CGFloat, to toAlpha: CGFloat, percent: CGFloat) -> CGFloat { 102 | let newAlpha = fromAlpha + (toAlpha - fromAlpha) * percent 103 | return newAlpha 104 | } 105 | } 106 | // MARK: - UINavigationBar 相关修改 107 | extension UINavigationBar { 108 | // MARK: property 109 | fileprivate struct AssociatedKeys { 110 | static var backgroundView: UIView = UIView() 111 | static var backgroundImageView: UIImageView = UIImageView() 112 | } 113 | 114 | fileprivate var backgroundView: UIView? { 115 | get { 116 | guard let bgView = objc_getAssociatedObject(self, &AssociatedKeys.backgroundView) as? UIView else { 117 | return nil 118 | } 119 | return bgView 120 | } 121 | set { 122 | objc_setAssociatedObject(self, &AssociatedKeys.backgroundView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 123 | } 124 | } 125 | 126 | fileprivate var backgroundImageView: UIImageView? { 127 | get { 128 | guard let bgImageView = objc_getAssociatedObject(self, &AssociatedKeys.backgroundImageView) as? UIImageView else { 129 | return nil 130 | } 131 | return bgImageView 132 | } 133 | set { 134 | objc_setAssociatedObject(self, &AssociatedKeys.backgroundImageView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 135 | } 136 | } 137 | // MARK: 设置导航栏自定义背景View 138 | public func vhl_setBackgroundView(_ view: UIView) { 139 | backgroundImageView?.removeFromSuperview() 140 | backgroundImageView = nil 141 | 142 | backgroundView?.removeFromSuperview() 143 | 144 | /// 这里需要将系统添加的模糊层隐藏,不然会在自己添加的背景层再添加一层模糊层 145 | if self.subviews.first?.subviews.count ?? 0 > 1 { 146 | if let backgroundEffectView = self.subviews.first?.subviews[1] { 147 | backgroundEffectView.alpha = 0.0 148 | } 149 | } 150 | 151 | if let viewCopyData = try? NSKeyedArchiver.archivedData(withRootObject: view, requiringSecureCoding: false) { 152 | // if let viewCopy = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIView.self, from: viewCopyData) { 153 | if let viewCopy = NSKeyedUnarchiver.unarchiveObject(with: viewCopyData) as? UIView { 154 | self.backgroundView = viewCopy 155 | if let backgroundView = self.backgroundView { 156 | backgroundView.frame = self.subviews.first!.bounds 157 | backgroundView.autoresizingMask = [.flexibleWidth, 158 | .flexibleHeight, 159 | .flexibleBottomMargin] 160 | /// iOS 11 下导航栏不显示问题 161 | if self.subviews.count > 0 { 162 | self.subviews.first?.insertSubview(backgroundView, at: 0) 163 | } else { 164 | self.insertSubview(backgroundView, at: 0) 165 | } 166 | } 167 | } 168 | } 169 | } 170 | // MARK: 设置导航栏背景图片 171 | public func vhl_setBackgroundImage(_ image: UIImage) { 172 | backgroundView?.removeFromSuperview() 173 | backgroundView = nil 174 | 175 | if backgroundImageView == nil { 176 | // add a image(nil color) to _UIBarBackground make it clear 177 | setBackgroundImage(UIImage(), for: .default) 178 | if let superView = self.subviews.first { 179 | self.backgroundImageView = UIImageView(frame: superView.bounds) 180 | if let backgroundImageView = self.backgroundImageView { 181 | backgroundImageView.tag = 10102 182 | backgroundImageView.autoresizingMask = [.flexibleWidth, 183 | .flexibleHeight, 184 | .flexibleBottomMargin] 185 | superView.insertSubview(backgroundImageView, at: 0) 186 | } 187 | } 188 | } 189 | self.backgroundImageView?.image = image 190 | } 191 | // MARK: 设置导航栏背景颜色 192 | public func vhl_setBackgroundColor(_ color: UIColor) { 193 | backgroundImageView?.removeFromSuperview() 194 | backgroundImageView = nil 195 | 196 | /// 这里自定义 bgView 也是使用的同一个变量 197 | let bgViewTag = 10101 198 | if backgroundView?.tag ?? 0 != bgViewTag { 199 | backgroundView?.removeFromSuperview() 200 | backgroundView = nil 201 | } 202 | 203 | if backgroundView == nil { 204 | if let superView = self.subviews.first { 205 | // add a image(nil color) to _UIBarBackground make it clear 206 | setBackgroundImage(UIImage(), for: .default) 207 | self.backgroundView = UIView(frame: superView.bounds) 208 | self.backgroundView?.tag = bgViewTag 209 | if let backgroundView = self.backgroundView { 210 | backgroundView.autoresizingMask = [.flexibleWidth, 211 | .flexibleHeight, 212 | .flexibleBottomMargin] 213 | superView.insertSubview(backgroundView, at: 0) 214 | } 215 | } 216 | } 217 | self.backgroundView?.backgroundColor = color 218 | } 219 | // MARK: 设置导航栏背景透明度 220 | public func vhl_setBackgroundAlpha(_ alpha: CGFloat) { 221 | // set _UIBarBackground alpha 222 | if let barBackgroundView = self.subviews.first { 223 | if #available(iOS 11.0, *) { // sometimes we can't change _UIBarBackground alpha 224 | for view in barBackgroundView.subviews { 225 | view.alpha = alpha 226 | } 227 | } 228 | barBackgroundView.alpha = alpha 229 | } 230 | } 231 | // MARK: 设置导航栏所有 barButtonItem 的透明度 232 | public func vhl_setBarButtonItemsAlpha(alpha: CGFloat, hasSystemBackIndicator:Bool) { 233 | for view in self.subviews { 234 | if hasSystemBackIndicator { 235 | if let _UIBarBackgroundClass = NSClassFromString("_UIBarBackground") { 236 | if !view.isKind(of: _UIBarBackgroundClass) { 237 | view.alpha = alpha 238 | } 239 | } 240 | if let _UINavigationBarBackground = NSClassFromString("_UINavigationBarBackground") { 241 | if !view.isKind(of: _UINavigationBarBackground) { 242 | view.alpha = alpha 243 | } 244 | } 245 | } else { 246 | // 这里如果不做判断的话,会显示 backIndicatorImage(系统返回按钮) 247 | if let _UINavigationBarBackIndicatorViewClass = NSClassFromString("_UINavigationBarBackIndicatorView"), 248 | !view.isKind(of: _UINavigationBarBackIndicatorViewClass) { 249 | if let _UIBarBackgroundClass = NSClassFromString("_UIBarBackground") { 250 | if view.isKind(of: _UIBarBackgroundClass) { 251 | view.alpha = alpha 252 | } 253 | } 254 | 255 | if let _UINavigationBarBackground = NSClassFromString("_UINavigationBarBackground") { 256 | if !view.isKind(of: _UINavigationBarBackground) { 257 | view.alpha = alpha 258 | } 259 | } 260 | } 261 | } 262 | } 263 | } 264 | // MARK: 设置默认的返回箭头是否隐藏 265 | public func vhl_setBarBackIndicatorViewIsHidden(_ isHidden: Bool) { 266 | for view in self.subviews { 267 | if let _UINavigationBarBackIndicatorViewClass = NSClassFromString("_UINavigationBarBackIndicatorView") { 268 | if view.isKind(of: _UINavigationBarBackIndicatorViewClass) { 269 | view.isHidden = isHidden 270 | } 271 | } 272 | } 273 | } 274 | // MARK: 分割线是否隐藏 275 | public func vhl_setShadowImageIsHidden(_ isHidden: Bool) { 276 | self.shadowImage = isHidden ? UIImage() : nil 277 | // iOS 11 后设置 shadowImage 无效 278 | self.setValue(isHidden, forKey: "hidesShadow") 279 | self.layoutIfNeeded() 280 | } 281 | // MARK: 设置/获取导航栏偏移量 282 | public func vhl_setTranslationY(y: CGFloat) { 283 | self.transform = CGAffineTransform(translationX: 0, y: y) 284 | } 285 | public func vhl_translationY() -> CGFloat { 286 | return self.transform.ty 287 | } 288 | 289 | // MARK: Hook methods 290 | private static let onceToken = UUID().uuidString 291 | static fileprivate func vhl_hookMethods() { 292 | DispatchQueue.vhl_once(token: onceToken) { 293 | let needSwizzleSelectorArr = [ 294 | #selector(setter: titleTextAttributes) 295 | ] 296 | 297 | for selector in needSwizzleSelectorArr { 298 | let str = ("vhl_" + selector.description) 299 | if let originalMethod = class_getInstanceMethod(self, selector), 300 | let swizzledMethod = class_getInstanceMethod(self, Selector(str)) { 301 | method_exchangeImplementations(originalMethod, swizzledMethod) 302 | } 303 | } 304 | } 305 | } 306 | @objc func vhl_setTitleTextAttributes(_ newTitleTextAttributes:[NSAttributedString.Key : Any]?) { 307 | guard var attributes = newTitleTextAttributes else { return } 308 | 309 | guard let originTitleTextAttributes = titleTextAttributes else { 310 | vhl_setTitleTextAttributes(attributes) 311 | return 312 | } 313 | 314 | var titleColor:UIColor? 315 | for attribute in originTitleTextAttributes { 316 | if attribute.key == NSAttributedString.Key.foregroundColor { 317 | titleColor = attribute.value as? UIColor 318 | break 319 | } 320 | } 321 | 322 | guard let originTitleColor = titleColor else { 323 | vhl_setTitleTextAttributes(attributes) 324 | return 325 | } 326 | 327 | if attributes[NSAttributedString.Key.foregroundColor] == nil { 328 | attributes.updateValue(originTitleColor, forKey: NSAttributedString.Key.foregroundColor) 329 | } 330 | 331 | // crash: 'Have you sent -vhl_setTitleTextAttributes: 332 | /// ** VHLNavigation.hook() 需要将在 viewController 初始化的最前面调用 ** 333 | vhl_setTitleTextAttributes(attributes) 334 | } 335 | } 336 | // MARK: - Hook UINavigationController 337 | extension UINavigationController: UINavigationBarDelegate { 338 | // MARK: 状态栏相关 339 | open override var preferredStatusBarStyle: UIStatusBarStyle { 340 | return topViewController?.preferredStatusBarStyle ?? UIStatusBarStyle.default 341 | } 342 | // MARK: 更新导航栏相关样式 343 | fileprivate func setNeedsNavigationBarUpdate(backgroundView: UIView) { // 导航栏背景视图 344 | navigationBar.vhl_setBackgroundView(backgroundView) 345 | } 346 | fileprivate func setNeedsNavigationBarUpdate(backgroundImage: UIImage) { // 导航栏背景图片 347 | navigationBar.vhl_setBackgroundImage(backgroundImage) 348 | } 349 | fileprivate func setNeedsNavigationBarUpdate(backgroundColor: UIColor) { // 导航栏背景颜色 350 | navigationBar.vhl_setBackgroundColor(backgroundColor) 351 | } 352 | fileprivate func setNeedsNavigationBarUpdate(backgroundAlpha: CGFloat) { // 导航栏背景透明度 353 | navigationBar.vhl_setBackgroundAlpha(backgroundAlpha) 354 | } 355 | fileprivate func setNeedsNavigationBarUpdate(tintColor: UIColor) { // 导航栏按钮颜色 356 | navigationBar.tintColor = tintColor 357 | } 358 | fileprivate func setNeedsNavigationBarUpdate(titleColor: UIColor) { // 导航栏标题颜色 359 | guard let titleTextAttributes = navigationBar.titleTextAttributes else { 360 | navigationBar.titleTextAttributes = [.foregroundColor: titleColor] 361 | return 362 | } 363 | var newTitleTextAttributes = titleTextAttributes 364 | newTitleTextAttributes.updateValue(titleColor, forKey: .foregroundColor) 365 | navigationBar.titleTextAttributes = newTitleTextAttributes 366 | } 367 | fileprivate func setNeedsNavigationBarUpdate(hidenShadowImage: Bool) { // 导航栏分割线隐藏 368 | navigationBar.vhl_setShadowImageIsHidden(hidenShadowImage) 369 | } 370 | 371 | // MARK: - Hook methods 372 | private static let onceToken = UUID().uuidString 373 | static fileprivate func vhl_navHookMethods() { // NavigationController 是继承自 vc 的 374 | DispatchQueue.vhl_once(token: onceToken) { 375 | let needSwizzleSelectorArr = [ 376 | NSSelectorFromString("_updateInteractiveTransition:"), 377 | #selector(popToViewController(_:animated:)), 378 | #selector(popToRootViewController(animated:)), 379 | #selector(pushViewController(_:animated:)), 380 | ] 381 | 382 | for selector in needSwizzleSelectorArr { 383 | // _updateInteractiveTransition: => vhl_updateInteractiveTransition: 384 | let str = ("vhl_" + selector.description).replacingOccurrences(of: "__", with: "_") 385 | if let originalMethod = class_getInstanceMethod(self, selector), 386 | let swizzledMethod = class_getInstanceMethod(self, Selector(str)) { 387 | method_exchangeImplementations(originalMethod, swizzledMethod) 388 | } 389 | } 390 | } 391 | } 392 | // MARK: hook popVC 393 | fileprivate struct VHLPOPProperties { 394 | static var popDuration = 0.13 // 侧滑动画时间 395 | static var displayCount = 0 // 396 | static var popProgress: CGFloat { // pop 进度 397 | let all:CGFloat = CGFloat(60.0 * popDuration) 398 | let current = min(all, CGFloat(displayCount)) 399 | return current / all 400 | } 401 | } 402 | @objc func vhl_popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { 403 | let displayLink = CADisplayLink(target: self, selector: #selector(popNeedDisplay)) 404 | displayLink.add(to: RunLoop.main, forMode: .common) 405 | CATransaction.setCompletionBlock { 406 | displayLink.invalidate() 407 | VHLPOPProperties.displayCount = 0 408 | } 409 | CATransaction.setAnimationDuration(VHLPOPProperties.popDuration) 410 | CATransaction.begin() 411 | let vcs = vhl_popToViewController(viewController, animated: animated) // 调用自己 412 | CATransaction.commit() 413 | return vcs 414 | } 415 | @objc func vhl_popToRootViewControllerAnimated(_ animated: Bool) -> [UIViewController]? { 416 | var displayLink: CADisplayLink? = CADisplayLink(target: self, selector: #selector(popNeedDisplay)) 417 | displayLink?.add(to: RunLoop.main, forMode: .common) 418 | CATransaction.setCompletionBlock { 419 | displayLink?.invalidate() 420 | displayLink = nil 421 | VHLPOPProperties.displayCount = 0 422 | } 423 | CATransaction.setAnimationDuration(VHLPOPProperties.popDuration) 424 | CATransaction.begin() 425 | let vcs = vhl_popToRootViewControllerAnimated(animated) // 调用自己 426 | CATransaction.commit() 427 | return vcs 428 | } 429 | // MARK: DisplayLink 侧滑监听 430 | @objc fileprivate func popNeedDisplay() { 431 | guard let topViewController = self.topViewController, let coordinator = topViewController.transitionCoordinator else { return } 432 | 433 | VHLPOPProperties.displayCount += 1 434 | let popProgress = VHLPOPProperties.popProgress 435 | let fromVC = coordinator.viewController(forKey: .from) 436 | let toVC = coordinator.viewController(forKey: .to) 437 | updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: popProgress) 438 | } 439 | // MARK: hook push vc 440 | fileprivate struct VHLPushProperties { 441 | static var pushDuration = 0.13 // 侧滑动画时间 442 | static var displayCount = 0 // 443 | static var pushProgress: CGFloat { // pop 进度 444 | let all:CGFloat = CGFloat(60.0 * pushDuration) 445 | let current = min(all, CGFloat(displayCount)) 446 | return current / all 447 | } 448 | } 449 | @objc func vhl_pushViewController(_ viewController: UIViewController, animated: Bool) { 450 | var displayLink: CADisplayLink? = CADisplayLink(target: self, selector: #selector(pushNeedDisplay)) 451 | displayLink?.add(to: RunLoop.main, forMode: .common) 452 | CATransaction.setCompletionBlock { 453 | displayLink?.invalidate() 454 | displayLink = nil 455 | VHLPushProperties.displayCount = 0 456 | viewController.pushToCurrentVCFinished = true 457 | } 458 | CATransaction.setAnimationDuration(VHLPushProperties.pushDuration) 459 | CATransaction.begin() 460 | vhl_pushViewController(viewController, animated: animated) // 调用自己 461 | CATransaction.commit() 462 | } 463 | @objc fileprivate func pushNeedDisplay() { 464 | guard let topViewController = self.topViewController, let coordinator = topViewController.transitionCoordinator else { return } 465 | if topViewController.isMotalFrom() { return } 466 | 467 | VHLPushProperties.displayCount += 1 468 | let pushProgress = VHLPushProperties.pushProgress 469 | let fromVC = coordinator.viewController(forKey: .from) 470 | let toVC = coordinator.viewController(forKey: .to) 471 | updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: pushProgress) 472 | } 473 | // MARK: hook 导航栏侧滑进度 474 | @objc func vhl_updateInteractiveTransition(_ percentComplete: CGFloat) { 475 | guard let topViewController = self.topViewController, let coordinator = topViewController.transitionCoordinator else { 476 | vhl_updateInteractiveTransition(percentComplete) 477 | return 478 | } 479 | let fromVC = coordinator.viewController(forKey: .from) 480 | let toVC = coordinator.viewController(forKey: .to) 481 | updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: percentComplete) 482 | // 调用自己 483 | vhl_updateInteractiveTransition(percentComplete) 484 | } 485 | 486 | // MARK: 根据进度更新导航栏 487 | fileprivate func updateNavigationBar(fromVC: UIViewController?, toVC: UIViewController?, progress: CGFloat) { 488 | // 背景颜色变化 489 | if let fromVC = fromVC, let toVC = toVC { 490 | // 是否是被忽略的 VC 491 | if fromVC.isIgnoreVC() || toVC.isIgnoreVC() { return } 492 | // 是否需要添加假的导航栏 493 | if self.topViewController?.shouldAddFakeNavigationBar() ?? false { return } 494 | 495 | // 颜色过渡 496 | // 1. 导航栏按钮颜色 497 | let fromTintColor = fromVC.vhl_navBarTintColor 498 | let toTintColor = toVC.vhl_navBarTintColor 499 | let newTintColor = VHLNavigation.middleColor(from: fromTintColor, to: toTintColor, percent: progress) 500 | self.setNeedsNavigationBarUpdate(tintColor: newTintColor) 501 | 502 | // 2. 导航栏标题颜色 503 | let fromTitleColor = fromVC.vhl_navBarTitleColor 504 | let toTitleColor = toVC.vhl_navBarTitleColor 505 | let newTitleColor = VHLNavigation.middleColor(from: fromTitleColor, to: toTitleColor, percent: progress) 506 | self.setNeedsNavigationBarUpdate(titleColor: newTitleColor) 507 | 508 | /// 背景颜色过渡时,判断是否有相同的自定义背景 View 509 | if fromVC.vhl_navBarBackgroundView != nil && 510 | self.topViewController?.fromToVCBackgroundViewIsSame() ?? false { return } 511 | 512 | // 3. 导航栏背景颜色 513 | let fromBackgroundColor = fromVC.vhl_navBarBackgroundColor 514 | let toBackgroundColor = toVC.vhl_navBarBackgroundColor 515 | let newBackgroundColor = VHLNavigation.middleColor(from: fromBackgroundColor, to: toBackgroundColor, percent: progress) 516 | self.setNeedsNavigationBarUpdate(backgroundColor: newBackgroundColor) 517 | 518 | // 4. 导航栏透明度 519 | let fromAlpha = fromVC.vhl_navBarBackgroundAlpha 520 | let toAlpha = toVC.vhl_navBarBackgroundAlpha 521 | let newAlpha = VHLNavigation.middleAlpha(from: fromAlpha, to: toAlpha, percent: progress) 522 | self.setNeedsNavigationBarUpdate(backgroundAlpha: newAlpha) 523 | } 524 | } 525 | // MARK: Delegate - UINavigationBarDelegate 526 | public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { 527 | if let topVC = self.topViewController, let coor = topVC.transitionCoordinator, coor.initiallyInteractive { 528 | if #available(iOS 10.0, *) { 529 | coor.notifyWhenInteractionChanges { (context) in 530 | self.dealInteractionChanges(context) 531 | } 532 | } else { 533 | coor.notifyWhenInteractionEnds { (context) in 534 | self.dealInteractionChanges(context) 535 | } 536 | } 537 | } 538 | let itemCount = navigationBar.items?.count ?? 0 539 | let n = self.viewControllers.count >= itemCount ? 2 : 1 540 | let popToVC = self.viewControllers[viewControllers.count - n] 541 | popToViewController(popToVC, animated: true) 542 | // fix: iOS 13 下点击导航栏返回按钮闪退 543 | if #available(iOS 13.0, *) { 544 | return false 545 | } 546 | return true 547 | } 548 | // deal the gesture of return break off 549 | private func dealInteractionChanges(_ context: UIViewControllerTransitionCoordinatorContext) { 550 | let animations: (UITransitionContextViewControllerKey) -> () = { 551 | if !(self.topViewController?.shouldAddFakeNavigationBar() ?? false) { 552 | let curBackgroundColor = context.viewController(forKey: $0)?.vhl_navBarBackgroundColor ?? VHLNavigation.def.navBackgroundColor 553 | let curBackgroundAlpha = context.viewController(forKey: $0)?.vhl_navBarBackgroundAlpha ?? VHLNavigation.def.navBackgroundAlpha 554 | 555 | self.setNeedsNavigationBarUpdate(backgroundColor: curBackgroundColor) 556 | self.setNeedsNavigationBarUpdate(backgroundAlpha: curBackgroundAlpha) 557 | } 558 | } 559 | if context.isCancelled { 560 | let cancelDuration = context.transitionDuration * Double(context.percentComplete) 561 | UIView.animate(withDuration: cancelDuration) { 562 | animations(.from) 563 | } 564 | } else { 565 | let finishDuration = context.transitionDuration * Double(1 - context.percentComplete) 566 | UIView.animate(withDuration: finishDuration) { 567 | animations(.to) 568 | } 569 | } 570 | } 571 | } 572 | extension UINavigationController { 573 | // 1. 是否支持自动转屏 574 | open override var shouldAutorotate: Bool { 575 | return self.topViewController?.shouldAutorotate ?? false 576 | } 577 | // 2. 支持哪些屏幕方向 578 | open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 579 | return self.topViewController?.supportedInterfaceOrientations ?? .portrait 580 | } 581 | // 4. 默认的屏幕方向(当前 ViewController 必须是通过模态出来的 UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 582 | open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 583 | return self.topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait 584 | } 585 | 586 | } 587 | // MARK: - Hook ViewController 588 | extension UIViewController { 589 | fileprivate struct AssociatedKeys { 590 | // 跳转到当前是否已完成 591 | static var pushToCurrentVCFinished: Bool = false 592 | // 跳转到下一个 VC 是否已完成 593 | static var pushToNextVCFinished: Bool = false 594 | 595 | // 当前导航栏切换样式 596 | static var navSwitchStyle: VHLNavigationSwitchStyle = .transition 597 | // 当前导航栏是否隐藏 598 | static var navBarHide: Bool = false 599 | // 当前导航栏背景视图 600 | static var navBarBackgroundView: UIView? 601 | // 当前导航栏背景图片 602 | static var navBarBackgroundImage: UIImage? 603 | // 当前导航栏背景颜色 604 | static var navBarBackgroundColor: UIColor = VHLNavigation.def.navBackgroundColor 605 | // 当前导航栏透明度 606 | static var navBarBackgroundAlpha: CGFloat = 1.0 607 | // 当前导航栏按钮颜色 608 | static var navBarTintColor: UIColor = VHLNavigation.def.navBarTintColor 609 | // 当前导航栏标题颜色 610 | static var navBarTitleColor: UIColor = VHLNavigation.def.navBarTitleColor 611 | // 当前导航栏底部分割线是否隐藏 612 | static var navBarShadowImageHide: Bool = VHLNavigation.def.navBarShadowImageHidden 613 | // 当前导航栏浮动高度 614 | static var navBarTranslationY: CGFloat = 0.0 615 | // 当前状态栏样式 616 | static var statusBarStyle: UIStatusBarStyle = .default 617 | // 当前侧滑手势是否可用 618 | static var interactivePopEnable: Bool = true 619 | 620 | // 假的导航栏 621 | static var fakeNavBar: UIImageView = UIImageView() 622 | static var tempBackView: UIView? 623 | } 624 | // MARK: property 625 | fileprivate var pushToCurrentVCFinished: Bool { 626 | get { 627 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.pushToCurrentVCFinished) as? Bool else { 628 | return false 629 | } 630 | return isFinished 631 | } 632 | set { 633 | objc_setAssociatedObject(self, &AssociatedKeys.pushToCurrentVCFinished, newValue, .OBJC_ASSOCIATION_ASSIGN) 634 | } 635 | } 636 | fileprivate var pushToNextVCFinished: Bool { 637 | get { 638 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.pushToNextVCFinished) as? Bool else { 639 | return false 640 | } 641 | return isFinished 642 | } 643 | set { 644 | objc_setAssociatedObject(self, &AssociatedKeys.pushToNextVCFinished, newValue, .OBJC_ASSOCIATION_ASSIGN) 645 | } 646 | } 647 | fileprivate var fakeNavBar: UIImageView? { 648 | get { 649 | return objc_getAssociatedObject(self, &AssociatedKeys.fakeNavBar) as? UIImageView 650 | } 651 | set { 652 | objc_setAssociatedObject(self, &AssociatedKeys.fakeNavBar, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 653 | } 654 | } 655 | fileprivate var tempBackView: UIView? { 656 | get { 657 | return objc_getAssociatedObject(self, &AssociatedKeys.tempBackView) as? UIView 658 | } 659 | set { 660 | objc_setAssociatedObject(self, &AssociatedKeys.tempBackView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 661 | } 662 | } 663 | // MARK: 公开属性 **** 664 | /// 当前导航栏切换样式 665 | var vhl_navSwitchStyle: VHLNavigationSwitchStyle { 666 | get { 667 | guard let style = objc_getAssociatedObject(self, &AssociatedKeys.navSwitchStyle) as? VHLNavigationSwitchStyle else { 668 | return .transition 669 | } 670 | return style 671 | } 672 | set { 673 | objc_setAssociatedObject(self, &AssociatedKeys.navSwitchStyle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 674 | } 675 | } 676 | /// 当前导航栏是否隐藏 677 | var vhl_navBarHide: Bool { 678 | get { 679 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.navBarHide) as? Bool else { 680 | return false 681 | } 682 | return isFinished 683 | } 684 | set { 685 | objc_setAssociatedObject(self, &AssociatedKeys.navBarHide, newValue, .OBJC_ASSOCIATION_ASSIGN) 686 | if pushToCurrentVCFinished { 687 | navigationController?.setNavigationBarHidden(newValue, animated: true) 688 | } 689 | } 690 | } 691 | /// 当前导航栏自定义背景 view 692 | var vhl_navBarBackgroundView: UIView? { 693 | get { 694 | let bgView = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundView) as? UIView 695 | return bgView 696 | } 697 | set { 698 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 699 | if pushToNextVCFinished == false { 700 | if let bgView = newValue { 701 | navigationController?.setNeedsNavigationBarUpdate(backgroundView: bgView) 702 | } 703 | } 704 | } 705 | } 706 | /// 当前导航栏背景图片 707 | var vhl_navBarBackgroundImage: UIImage? { 708 | get { 709 | let bgImage = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundImage) as? UIImage 710 | return bgImage 711 | } 712 | set { 713 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundImage, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 714 | if pushToNextVCFinished == false { 715 | if let bgImage = newValue { 716 | navigationController?.setNeedsNavigationBarUpdate(backgroundImage: bgImage) 717 | } 718 | } 719 | } 720 | } 721 | /// 当前导航栏背景颜色 722 | var vhl_navBarBackgroundColor: UIColor { 723 | get { 724 | guard let bgColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundColor) as? UIColor else { 725 | return VHLNavigation.def.navBackgroundColor 726 | } 727 | return bgColor 728 | } 729 | set { 730 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 731 | if pushToNextVCFinished == false { 732 | navigationController?.setNeedsNavigationBarUpdate(backgroundColor: newValue) 733 | } 734 | } 735 | } 736 | /// 当前导航栏背景透明度 737 | var vhl_navBarBackgroundAlpha: CGFloat { 738 | get { 739 | guard let bgColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundAlpha) as? CGFloat else { 740 | return 1.0 741 | } 742 | return bgColor 743 | } 744 | set { 745 | objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundAlpha, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 746 | if pushToNextVCFinished == false { 747 | navigationController?.setNeedsNavigationBarUpdate(backgroundAlpha: newValue) 748 | } 749 | } 750 | } 751 | /// 当前导航栏按钮颜色 752 | var vhl_navBarTintColor: UIColor { 753 | get { 754 | guard let tintColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarTintColor) as? UIColor else { 755 | return VHLNavigation.def.navBarTintColor 756 | } 757 | return tintColor 758 | } 759 | set { 760 | objc_setAssociatedObject(self, &AssociatedKeys.navBarTintColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 761 | 762 | if pushToNextVCFinished == false { 763 | navigationController?.setNeedsNavigationBarUpdate(tintColor: newValue) 764 | } 765 | } 766 | } 767 | /// 当前导航栏标题颜色 768 | var vhl_navBarTitleColor: UIColor { 769 | get { 770 | guard let tintColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarTitleColor) as? UIColor else { 771 | return VHLNavigation.def.navBarTitleColor 772 | } 773 | return tintColor 774 | } 775 | set { 776 | objc_setAssociatedObject(self, &AssociatedKeys.navBarTitleColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 777 | 778 | if pushToNextVCFinished == false { 779 | navigationController?.setNeedsNavigationBarUpdate(titleColor: newValue) 780 | } 781 | } 782 | } 783 | /// 当前导航栏分割线是否隐藏 784 | var vhl_navBarShadowImageHide: Bool { 785 | get { 786 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.navBarShadowImageHide) as? Bool else { 787 | return VHLNavigation.def.navBarShadowImageHidden 788 | } 789 | return isFinished 790 | } 791 | set { 792 | objc_setAssociatedObject(self, &AssociatedKeys.navBarShadowImageHide, newValue, .OBJC_ASSOCIATION_ASSIGN) 793 | if pushToNextVCFinished == false { 794 | self.navigationController?.setNeedsNavigationBarUpdate(hidenShadowImage: newValue) 795 | } 796 | } 797 | } 798 | /// 当前状态栏样式 799 | var vhl_statusBarStyle: UIStatusBarStyle { 800 | get { 801 | guard let style = objc_getAssociatedObject(self, &AssociatedKeys.statusBarStyle) as? UIStatusBarStyle else { 802 | return .default 803 | } 804 | return style 805 | } 806 | set { 807 | objc_setAssociatedObject(self, &AssociatedKeys.statusBarStyle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 808 | setNeedsStatusBarAppearanceUpdate() 809 | } 810 | } 811 | /// 当前侧滑手势是否可用 812 | var vhl_interactivePopEnable: Bool { 813 | get { 814 | guard let isFinished = objc_getAssociatedObject(self, &AssociatedKeys.interactivePopEnable) as? Bool else { 815 | return true 816 | } 817 | return isFinished 818 | } 819 | set { 820 | objc_setAssociatedObject(self, &AssociatedKeys.interactivePopEnable, newValue, .OBJC_ASSOCIATION_ASSIGN) 821 | updateInteractivePopGestureRecognizer() 822 | } 823 | } 824 | /// 当前导航栏偏移 825 | var vhl_navTranslationY: CGFloat { 826 | get { 827 | let transY = objc_getAssociatedObject(self, &AssociatedKeys.navBarTranslationY) as? CGFloat ?? 0.0 828 | return transY 829 | } 830 | set { 831 | objc_setAssociatedObject(self, &AssociatedKeys.navBarTranslationY, newValue, .OBJC_ASSOCIATION_ASSIGN) 832 | self.navigationController?.navigationBar.vhl_setTranslationY(y: newValue) 833 | } 834 | } 835 | // MARK: - Hook methods 836 | private static let onceToken = UUID().uuidString 837 | static fileprivate func vhl_hookMethods() { 838 | DispatchQueue.vhl_once(token: onceToken) { 839 | let needSwizzleSelectors = [ 840 | #selector(viewWillAppear(_:)), 841 | #selector(viewWillDisappear(_:)), 842 | #selector(viewDidAppear(_:)), 843 | #selector(viewDidDisappear(_:)) 844 | ] 845 | // 交换方法 846 | for selector in needSwizzleSelectors{ 847 | let newSelectorStr = "vhl_" + selector.description 848 | if let originalMethod = class_getInstanceMethod(self, selector), 849 | let swizzledMethod = class_getInstanceMethod(self, Selector(newSelectorStr)) { 850 | method_exchangeImplementations(originalMethod, swizzledMethod) 851 | } 852 | } 853 | } 854 | } 855 | @objc private func vhl_viewWillAppear(_ animated: Bool) { 856 | if self.canUpdateNavigationBar() && !self.isIgnoreVC() { 857 | self.pushToNextVCFinished = false 858 | self.setNeedsStatusBarAppearanceUpdate() // 更新状态栏 859 | if let navVC = self.navigationController { 860 | // 导航栏返回按钮 861 | if navVC.viewControllers.count == 1 { 862 | navVC.navigationBar.vhl_setBarBackIndicatorViewIsHidden(true) 863 | } else { 864 | navVC.navigationBar.vhl_setBarBackIndicatorViewIsHidden(false) 865 | } 866 | // 当前导航栏是否隐藏 867 | navVC.setNavigationBarHidden(self.vhl_navBarHide, animated: animated) 868 | // 恢复导航栏偏移 869 | self.vhl_navTranslationY = 0.0 // 修复导航栏偏移 870 | // 添加一个假的导航栏 871 | if self.shouldAddFakeNavigationBar() { 872 | self.addFakeNavigationBar() 873 | } 874 | // 更新导航栏信息 875 | if !self.vhl_navBarHide && !self.isIgnoreVC() { 876 | let isModal = (self.isMotalFrom() || self.isMotalTo()) 877 | 878 | // ** 当两个VC都是颜色过渡的时候,这里不设置背景,不然会闪动一下 ** 879 | // ** 模态跳转下,需要更新导航背景,不然有概率出现白色背景 880 | if self.fakeNavBar != nil && (!self.isNavTransition() || self.isRootViewController()) || isModal { 881 | self.updateNavigationBackground() 882 | } 883 | navVC.setNeedsNavigationBarUpdate(tintColor: self.vhl_navBarTintColor) 884 | navVC.setNeedsNavigationBarUpdate(titleColor: self.vhl_navBarTitleColor) 885 | } 886 | } 887 | } 888 | // 调用自己 889 | vhl_viewWillAppear(animated) 890 | } 891 | @objc private func vhl_viewDidAppear(_ animated: Bool) { 892 | if !self.isRootViewController() { self.pushToCurrentVCFinished = true } 893 | self.removeFakeNavigationBar() // 移除假的导航栏 894 | 895 | if self.canUpdateNavigationBar() { 896 | if let navVC = self.navigationController { 897 | if self.isIgnoreVC() { // 如果是忽略的 vc 898 | navVC.setNeedsNavigationBarUpdate(backgroundAlpha: 1.0) 899 | navVC.setNeedsNavigationBarUpdate(tintColor: navVC.navigationBar.tintColor) 900 | navVC.setNeedsNavigationBarUpdate(backgroundImage: UIImage()) 901 | if let barTintColor = navVC.navigationBar.barTintColor { 902 | navVC.setNeedsNavigationBarUpdate(backgroundColor: barTintColor) 903 | } 904 | } else { 905 | self.updateNavigationAllInfo() 906 | } 907 | } 908 | } 909 | self.updateInteractivePopGestureRecognizer() 910 | // 调用自己 911 | vhl_viewDidAppear(animated) 912 | } 913 | @objc private func vhl_viewWillDisappear(_ animated: Bool) { 914 | if self.canUpdateNavigationBar() && !self.isIgnoreVC() && !self.isMotalTo() { 915 | // 取消隐藏导航栏 916 | self.navigationController?.setNavigationBarHidden(self.vhl_navBarHide, animated: animated) // self.vhl_navBarHide 917 | self.pushToNextVCFinished = true 918 | } 919 | // 调用自己 920 | vhl_viewWillDisappear(animated) 921 | } 922 | @objc private func vhl_viewDidDisappear(_ animated: Bool) { 923 | if self.canUpdateNavigationBar() { 924 | // 移除假的导航栏 925 | self.removeFakeNavigationBar() 926 | // 恢复导航栏浮动 927 | self.vhl_navTranslationY = 0 928 | } 929 | // 调用自己 930 | vhl_viewDidDisappear(animated) 931 | } 932 | } 933 | fileprivate extension UIViewController { 934 | // MARK: 判断当前是否能更新导航栏 935 | func canUpdateNavigationBar() -> Bool { 936 | guard let navigationController = self.navigationController else { 937 | return false 938 | } 939 | return navigationController.viewControllers.contains(self) 940 | } 941 | // MARK: 更新导航栏背景(背景视图/背景图片/背景颜色) 942 | func updateNavigationBackground() { 943 | if let navVC = self.navigationController { 944 | if let bgView = self.vhl_navBarBackgroundView { 945 | navVC.setNeedsNavigationBarUpdate(backgroundView: bgView) 946 | } else if let bgImage = self.vhl_navBarBackgroundImage { 947 | navVC.setNeedsNavigationBarUpdate(backgroundImage: bgImage) 948 | } else { 949 | navVC.setNeedsNavigationBarUpdate(backgroundColor: self.vhl_navBarBackgroundColor) 950 | } 951 | } 952 | } 953 | // MARK: 更新导航栏的所有属性信息 954 | func updateNavigationAllInfo() { 955 | // 背景 956 | updateNavigationBackground() 957 | // 导航栏按钮/标题/透明度/分割线 958 | if let navVC = self.navigationController { 959 | navVC.setNeedsNavigationBarUpdate(tintColor: self.vhl_navBarTintColor) 960 | navVC.setNeedsNavigationBarUpdate(titleColor: self.vhl_navBarTitleColor) 961 | navVC.setNeedsNavigationBarUpdate(backgroundAlpha: self.vhl_navBarBackgroundAlpha) 962 | navVC.setNeedsNavigationBarUpdate(hidenShadowImage: self.vhl_navBarShadowImageHide) 963 | } 964 | } 965 | // MARK: 更新侧滑手势开启关闭 966 | func updateInteractivePopGestureRecognizer() { 967 | if let navVC = self.navigationController { 968 | if let panRecognizer = navVC.interactivePopGestureRecognizer { 969 | if navVC.viewControllers.count <= 1 { 970 | panRecognizer.isEnabled = false 971 | } else { 972 | panRecognizer.isEnabled = self.vhl_interactivePopEnable 973 | } 974 | } 975 | } 976 | } 977 | } 978 | fileprivate extension UIViewController { 979 | // MARK: 导航栏是否是颜色过渡 980 | func isNavTransition() -> Bool { 981 | guard let fromVC = self.fromVC() else { return false } 982 | guard let toVC = self.toVC() else { return false } 983 | // 984 | if fromVC.vhl_navBarHide || toVC.vhl_navBarHide { return false } 985 | if fromVC.vhl_navBarBackgroundView != nil || toVC.vhl_navBarBackgroundView != nil { return false } 986 | if fromVC.vhl_navBarBackgroundImage != nil || toVC.vhl_navBarBackgroundImage != nil { return false } 987 | if fromVC.vhl_navSwitchStyle == .fakeNavBar || toVC.vhl_navSwitchStyle == .fakeNavBar { return false } 988 | if fromVC.vhl_navBarBackgroundAlpha != toVC.vhl_navBarBackgroundAlpha { return false } 989 | 990 | return true 991 | } 992 | // MARK: 是否是被忽略的 viewController 993 | func isIgnoreVC() -> Bool { 994 | if let selfClassName = NSStringFromClass(self.classForCoder).components(separatedBy: ".").last { 995 | return VHLNavigation.def.isIgnoreVC(selfClassName) 996 | } 997 | return false 998 | } 999 | } 1000 | extension UIViewController { 1001 | // 判断当前是否是第一个视图 1002 | func isRootViewController() -> Bool { 1003 | if self.navigationController?.viewControllers.count ?? 0 > 1 { return false } 1004 | 1005 | if let rootVC = self.navigationController?.viewControllers.first { 1006 | if rootVC.isKind(of: UITabBarController.self) { 1007 | if let tabbarVC = rootVC as? UITabBarController { 1008 | return tabbarVC.viewControllers?.contains(self) ?? false 1009 | } 1010 | } else { 1011 | return rootVC == self 1012 | } 1013 | } 1014 | return false 1015 | } 1016 | /// 当前是否进行了模态跳转 1017 | func isMotalTo() -> Bool { 1018 | if self.presentedViewController != nil { return true } 1019 | return false 1020 | } 1021 | /// 当前是否是模态跳转而来 1022 | func isMotalFrom() -> Bool { 1023 | if let viewControllers = self.navigationController?.viewControllers { 1024 | if viewControllers.count > 1 { 1025 | if viewControllers.last! == self { 1026 | return false 1027 | } 1028 | } 1029 | } 1030 | if self.presentingViewController != nil { return true } 1031 | return false 1032 | } 1033 | 1034 | // MARK: from VC 1035 | func fromVC() -> UIViewController? { 1036 | return self.navigationController?.topViewController?.transitionCoordinator?.viewController(forKey: .from) 1037 | } 1038 | // MARK: to VC 1039 | func toVC() -> UIViewController? { 1040 | return self.navigationController?.topViewController?.transitionCoordinator?.viewController(forKey: .to) 1041 | } 1042 | // MARK: 导航栏高度 1043 | func statusBarHeight() -> CGFloat { 1044 | return UIApplication.shared.statusBarFrame.height 1045 | } 1046 | func navigationBarHeight() -> CGFloat { 1047 | return self.navigationController?.navigationBar.bounds.height ?? 0.0 1048 | } 1049 | func navigationBarAndStatusBarHeight() -> CGFloat { 1050 | let navHeight = self.navigationBarHeight() 1051 | var statusHeight = self.statusBarHeight() 1052 | // 分享热点,拨打电话等,导航栏变低 1053 | if !VHLNavigation.isIPhoneXSeries() { 1054 | statusHeight = min(20, statusHeight) 1055 | } 1056 | 1057 | return navHeight + statusHeight 1058 | } 1059 | } 1060 | // MARK: 假导航栏操作 1061 | fileprivate extension UIViewController { 1062 | // MARK: 切换的导航栏自定义 View 是否一样 1063 | func fromToVCBackgroundViewIsSame() -> Bool { 1064 | guard let fromVC = self.fromVC() else { return false } 1065 | guard let toVC = self.toVC() else { return false } 1066 | 1067 | if (fromVC.vhl_navBarBackgroundView == nil) && (toVC.vhl_navBarBackgroundView == nil) { return true } 1068 | if (fromVC.vhl_navBarBackgroundView == nil) != (toVC.vhl_navBarBackgroundView == nil) { return false } 1069 | if let fromBGView = fromVC.vhl_navBarBackgroundView, let toBGView = toVC.vhl_navBarBackgroundView { 1070 | if fromBGView.classForCoder != toBGView.classForCoder { return false } 1071 | if fromBGView.tag == 0 || fromBGView.tag != toBGView.tag { return false } 1072 | 1073 | return true 1074 | } 1075 | return false 1076 | } 1077 | // MARK: 判断当前是否需要添加一个假的导航栏 1078 | func shouldAddFakeNavigationBar() -> Bool { 1079 | guard let fromVC = self.fromVC() else { return false } 1080 | guard let toVC = self.toVC() else { return false } 1081 | if fromVC.isIgnoreVC() || toVC.isIgnoreVC() { return false } 1082 | 1083 | if fromVC.vhl_navSwitchStyle == .fakeNavBar || toVC.vhl_navSwitchStyle == .fakeNavBar { return true } 1084 | if fromVC.vhl_navBarBackgroundImage != nil || toVC.vhl_navBarBackgroundImage != nil { return true } 1085 | if fromVC.vhl_navBarHide != toVC.vhl_navBarHide { return true } 1086 | if fromVC.vhl_navBarBackgroundAlpha != toVC.vhl_navBarBackgroundAlpha { return true } 1087 | // 自定义背景 View。如果自定义 view 类型一样,且 tag 也一样,不执行假导航栏过渡 1088 | if !self.fromToVCBackgroundViewIsSame() { return true } 1089 | 1090 | return false 1091 | } 1092 | // MARK: 添加一个假的导航栏 1093 | func addFakeNavigationBar() { 1094 | guard let fromVC = self.fromVC() else { return } 1095 | guard let toVC = self.toVC() else { return } 1096 | 1097 | fromVC.removeFakeNavigationBar() 1098 | toVC.removeFakeNavigationBar() 1099 | 1100 | if fromVC.isIgnoreVC() && toVC.isIgnoreVC() { return } 1101 | 1102 | let isTranslucent = self.navigationController?.navigationBar.isTranslucent ?? true 1103 | 1104 | if !fromVC.vhl_navBarHide { 1105 | // 添加假导航栏 1106 | var fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarAndStatusBarHeight()) 1107 | if #available(iOS 13.0, *) { // iOS 13 模态跳转 1108 | if let navVC = self.navigationController { 1109 | if fromVC.isMotalFrom() && navVC.modalPresentationStyle == .pageSheet { 1110 | fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarHeight()) 1111 | } 1112 | } 1113 | } 1114 | // 2. 判断当前 vc 是否是 UITableViewController 或 UICollectionViewController , 因为这种 vc.view 会为 scrollview 1115 | // ** 虽然 view frame 为全屏开始,但是因为安全区域,使得内容视图在导航栏下面 ** 1116 | if fromVC.view.isKind(of: UIScrollView.self) || 1117 | fromVC.edgesForExtendedLayout == UIRectEdge.init(rawValue: 0) || 1118 | fromVC.view.bounds.height < fromVC.navigationController?.view.bounds.height ?? 0.0 { 1119 | fakeNavFrame = fromVC.view.convert(fakeNavFrame, from: fromVC.navigationController?.view) 1120 | } 1121 | // 1122 | fromVC.fakeNavBar = UIImageView(frame: fakeNavFrame) 1123 | if let fakeNavBar = fromVC.fakeNavBar { 1124 | if let bgView = fromVC.vhl_navBarBackgroundView { 1125 | fakeNavBar.backgroundColor = .clear 1126 | 1127 | /// ** 这里需要对 view 进行拷贝,避免 UINavigationBar 中对 bgView 移除操作 1128 | if let viewCopyData = try? NSKeyedArchiver.archivedData(withRootObject: bgView, requiringSecureCoding: false) { 1129 | // if let viewCopy = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIView.self, from: bgViewCopyData) { 1130 | if let viewCopy = NSKeyedUnarchiver.unarchiveObject(with: viewCopyData) as? UIView { 1131 | viewCopy.frame = fakeNavFrame 1132 | fakeNavBar.addSubview(viewCopy) 1133 | } 1134 | } 1135 | } else { 1136 | fakeNavBar.backgroundColor = fromVC.vhl_navBarBackgroundColor 1137 | if #available(iOS 13.0, *) { // iOS 13 下导航栏透明度问题 1138 | /// 非 .clear 的情况 1139 | if fromVC.vhl_navBarBackgroundColor != UIColor.clear { 1140 | fakeNavBar.backgroundColor = fromVC.vhl_navBarBackgroundColor.withAlphaComponent(fromVC.vhl_navBarBackgroundAlpha) 1141 | } 1142 | } 1143 | fakeNavBar.image = fromVC.vhl_navBarBackgroundImage 1144 | // 如果是忽略的VC,且默认的背景颜色有值 1145 | if let barTintColor = fromVC.navigationController?.navigationBar.barTintColor { 1146 | if fromVC.isIgnoreVC() { 1147 | fakeNavBar.backgroundColor = barTintColor 1148 | } 1149 | } 1150 | } 1151 | 1152 | fakeNavBar.alpha = fromVC.vhl_navBarBackgroundAlpha // 导航栏透明度 1153 | fromVC.view.addSubview(fakeNavBar) 1154 | fromVC.view.bringSubviewToFront(fakeNavBar) 1155 | // 隐藏系统导航栏背景 1156 | fromVC.navigationController?.setNeedsNavigationBarUpdate(backgroundAlpha: 0.0) 1157 | 1158 | // - 当从有状态栏切换到无状态栏时,会出现一个当前 vc 显示了底部 vc 的内容,这里增加一个 view 用于遮盖 1159 | var tempFakeNavBarFrame = fakeNavFrame 1160 | tempFakeNavBarFrame.size.height = tempFakeNavBarFrame.height + 20.0 1161 | fromVC.tempBackView = UIView(frame: tempFakeNavBarFrame) 1162 | if let tempBackView = fromVC.tempBackView { 1163 | tempBackView.backgroundColor = fromVC.view.backgroundColor 1164 | fromVC.view.addSubview(tempBackView) 1165 | fromVC.view.sendSubviewToBack(tempBackView) 1166 | } 1167 | } 1168 | } 1169 | 1170 | if !toVC.vhl_navBarHide { 1171 | // 添加假导航栏 1172 | var fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarAndStatusBarHeight()) 1173 | 1174 | if #available(iOS 13.0, *) { // iOS 13 模态跳转 1175 | if let navVC = self.navigationController { 1176 | if fromVC.isMotalFrom() && navVC.modalPresentationStyle == .pageSheet { 1177 | fakeNavFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.navigationBarHeight()) 1178 | } 1179 | } 1180 | } 1181 | 1182 | // 当前 fromVC 隐藏导航栏,且 isTranslucent = false 时。toVC 的 fakeNav 位置会不正确 1183 | if fromVC.vhl_navBarHide && !isTranslucent { 1184 | fakeNavFrame.origin.y = -fakeNavFrame.height 1185 | } 1186 | 1187 | // 2. 判断当前 vc 是否是 UITableViewController 或 UICollectionViewController , 因为这种 vc.view 会为 scrollview 1188 | // ** 虽然 view frame 为全屏开始,但是因为安全区域,使得内容视图在导航栏下面 ** 1189 | if toVC.view.isKind(of: UIScrollView.self) || 1190 | toVC.edgesForExtendedLayout == UIRectEdge.init(rawValue: 0) || 1191 | toVC.view.bounds.height < toVC.navigationController?.view.bounds.height ?? 0.0 { 1192 | fakeNavFrame = toVC.view.convert(fakeNavFrame, from: toVC.navigationController?.view) 1193 | } 1194 | // 1195 | toVC.fakeNavBar = UIImageView(frame: fakeNavFrame) 1196 | if let fakeNavBar = toVC.fakeNavBar { 1197 | if let bgView = toVC.vhl_navBarBackgroundView { 1198 | fakeNavBar.backgroundColor = .clear 1199 | 1200 | if let viewCopyData = try? NSKeyedArchiver.archivedData(withRootObject: bgView, requiringSecureCoding: false) { 1201 | // if let bgViewCopy = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIView.self, from: bgViewCopyData) { 1202 | if let viewCopy = NSKeyedUnarchiver.unarchiveObject(with: viewCopyData) as? UIView { 1203 | viewCopy.frame = fakeNavFrame 1204 | fakeNavBar.addSubview(viewCopy) 1205 | } 1206 | } 1207 | } else { 1208 | fakeNavBar.backgroundColor = toVC.vhl_navBarBackgroundColor 1209 | if #available(iOS 13.0, *) { // iOS 13 下导航栏透明度问题 1210 | /// 非 .clear 的情况 1211 | if toVC.vhl_navBarBackgroundColor != UIColor.clear { 1212 | fakeNavBar.backgroundColor = toVC.vhl_navBarBackgroundColor.withAlphaComponent(toVC.vhl_navBarBackgroundAlpha) 1213 | } 1214 | } 1215 | fakeNavBar.image = toVC.vhl_navBarBackgroundImage 1216 | // 如果是忽略的VC,且默认的背景颜色有值 1217 | if let barTintColor = toVC.navigationController?.navigationBar.barTintColor { 1218 | if toVC.isIgnoreVC() { 1219 | fakeNavBar.backgroundColor = barTintColor 1220 | } 1221 | } 1222 | } 1223 | fakeNavBar.alpha = toVC.vhl_navBarBackgroundAlpha // 导航栏透明度 1224 | toVC.view.addSubview(fakeNavBar) 1225 | toVC.view.bringSubviewToFront(fakeNavBar) 1226 | // 隐藏系统导航栏背景 1227 | toVC.navigationController?.setNeedsNavigationBarUpdate(backgroundAlpha: 0.0) 1228 | } 1229 | } 1230 | } 1231 | // 移除假的导航栏 1232 | func removeFakeNavigationBar() { 1233 | if let fakeNavBar = self.fakeNavBar { 1234 | fakeNavBar.removeFromSuperview() 1235 | self.fakeNavBar = nil 1236 | } 1237 | if let tempBackView = self.tempBackView { 1238 | tempBackView.removeFromSuperview() 1239 | self.tempBackView = nil 1240 | } 1241 | } 1242 | } 1243 | // MARK: - DispatchQueue 单例执行 1244 | fileprivate extension DispatchQueue { 1245 | private static var onceTracker = [String]() 1246 | //Executes a block of code, associated with a unique token, only once. The code is thread safe and will only execute the code once even in the presence of multithreaded calls. 1247 | static func vhl_once(token: String, block: () -> Void) { // 保证被 objc_sync_enter 和 objc_sync_exit 包裹的代码可以有序同步地执行 1248 | objc_sync_enter(self) 1249 | defer { // 作用域结束后执行defer中的代码 1250 | objc_sync_exit(self) 1251 | } 1252 | 1253 | if onceTracker.contains(token) { 1254 | return 1255 | } 1256 | 1257 | onceTracker.append(token) 1258 | block() 1259 | } 1260 | } 1261 | // MARK: - UIApplication 程序第一次运行时注入运行时注入 1262 | // 这里需要自己手动调用进行注入 1263 | extension UIApplication { 1264 | /// ** VHLNavigation.hook() 需要将在 viewController 初始化的最前面调用 ** 1265 | static func VHLNavigationHook() { 1266 | VHLNavigation.hook() 1267 | } 1268 | static let VHLNavigation_runOnce: Void = { // 使用静态变量。用于只调用一次 1269 | VHLNavigation.hook() 1270 | }() 1271 | // iOS 13.4 下因为有 UIScene 会不生效 1272 | // open override var next: UIResponder? { 1273 | // UIApplication.VHLNavigation_runOnce 1274 | // return super.next 1275 | // } 1276 | } 1277 | 1278 | /** 1279 | 注意: 1280 | 1. 隐藏导航栏后,侧滑手势需要自己实现一个 UINavigationController 导航栏基类,不然侧滑手势不可用 1281 | self.interactivePopGestureRecognizer?.delegate = self 1282 | func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 1283 | if self.viewControllers.count <= 1 { 1284 | return false 1285 | } 1286 | return true 1287 | } 1288 | 1289 | 2. 1290 | */ 1291 | -------------------------------------------------------------------------------- /VHLNavigation_Swift.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | 4 | s.name = "VHLNavigation_Swift" 5 | s.version = "1.0.2" 6 | 7 | s.summary = "navigationbar color / Dynamic blur / background image/ alpha / hidden" 8 | s.homepage = "https://github.com/huanglins/VHLNavigation_Swift" 9 | s.license = { :type => "MIT", :file => "LICENSE" } 10 | s.author = { "vincent" => "gvincent@163.com" } 11 | s.platform = :ios, "9.0" 12 | s.source = { :git => "https://github.com/huanglins/VHLNavigation_Swift.git", :tag => s.version } 13 | s.source_files = "VHLNavigation/*.{h,m,swift}" 14 | s.requires_arc = true 15 | s.social_media_url = "https://github.com/huanglins" 16 | 17 | s.swift_version = '5.0' 18 | 19 | end -------------------------------------------------------------------------------- /screenshots/导航栏滚动.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/导航栏滚动.gif -------------------------------------------------------------------------------- /screenshots/微信样式.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/微信样式.gif -------------------------------------------------------------------------------- /screenshots/背景图片.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/背景图片.gif -------------------------------------------------------------------------------- /screenshots/自定义View.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/自定义View.gif -------------------------------------------------------------------------------- /screenshots/透明度.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/透明度.gif -------------------------------------------------------------------------------- /screenshots/隐藏导航栏.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/隐藏导航栏.gif -------------------------------------------------------------------------------- /screenshots/颜色过渡.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglins/VHLNavigation_Swift/291452169692db79c4f7b38802be7ec9ffa4d898/screenshots/颜色过渡.gif --------------------------------------------------------------------------------