├── LICENSE ├── PageTest ├── PageTest.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── xiaocan.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── xiaocan.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── PageTest │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── DeviceInfo │ ├── DeviceInfoDefine.h │ ├── SFHFKeychainUtils.h │ ├── SFHFKeychainUtils.m │ ├── XC_DeviceInfo.h │ └── XC_DeviceInfo.m │ ├── FlowLayoutTest │ ├── LayoutControlCell.h │ ├── LayoutControlCell.m │ ├── LayoutControlCell.xib │ ├── LayoutController.h │ └── LayoutController.m │ ├── Info.plist │ ├── PageController.h │ ├── PageController.m │ ├── PrefixHeader.pch │ ├── UIShadow+UIView │ ├── UIShadow+UIView.h │ └── UIShadow+UIView.m │ ├── ViewController.h │ ├── ViewController.m │ ├── WaterLayoutTest │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── WaterHorizontalCell.h │ ├── WaterHorizontalCell.m │ ├── WaterHorizontalCell.xib │ ├── WaterViewController.h │ └── WaterViewController.m │ ├── cover.jpg │ └── main.m ├── README.md ├── Resources ├── affine.gif ├── all.gif ├── horizontal.gif ├── lineLayout.gif └── vertical.gif ├── XCollectionViewLayout.podspec └── XCollectionViewLayout ├── XCWaterCollectionViewLayout.h ├── XCWaterCollectionViewLayout.m ├── XCollectionViewLayout.h └── XCollectionViewLayout.m /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 xiaocan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PageTest/PageTest.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 72437BCA22B14F2E0082F81C /* LayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 72437BC922B14F2E0082F81C /* LayoutController.m */; }; 11 | 72437BCE22B150670082F81C /* LayoutControlCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 72437BCC22B150670082F81C /* LayoutControlCell.m */; }; 12 | 72437BCF22B150670082F81C /* LayoutControlCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 72437BCD22B150670082F81C /* LayoutControlCell.xib */; }; 13 | 726C65FE22AF2FA800C64025 /* XC_DeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 726C65FD22AF2FA800C64025 /* XC_DeviceInfo.m */; }; 14 | 726C660122AF30BA00C64025 /* SFHFKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 726C660022AF30BA00C64025 /* SFHFKeychainUtils.m */; }; 15 | 726C660522AF583900C64025 /* cover.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 726C660422AF583800C64025 /* cover.jpg */; }; 16 | 726C660822AF592800C64025 /* UIShadow+UIView.m in Sources */ = {isa = PBXBuildFile; fileRef = 726C660722AF592800C64025 /* UIShadow+UIView.m */; }; 17 | 72B4759822B6703E004670D8 /* WaterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 72B4759722B6703E004670D8 /* WaterViewController.m */; }; 18 | 72B4759E22B687FA004670D8 /* 1.png in Resources */ = {isa = PBXBuildFile; fileRef = 72B4759A22B687FA004670D8 /* 1.png */; }; 19 | 72B4759F22B687FA004670D8 /* 4.png in Resources */ = {isa = PBXBuildFile; fileRef = 72B4759B22B687FA004670D8 /* 4.png */; }; 20 | 72B475A022B687FA004670D8 /* 3.png in Resources */ = {isa = PBXBuildFile; fileRef = 72B4759C22B687FA004670D8 /* 3.png */; }; 21 | 72B475A122B687FA004670D8 /* 2.png in Resources */ = {isa = PBXBuildFile; fileRef = 72B4759D22B687FA004670D8 /* 2.png */; }; 22 | 72D736F92293A5C000951D30 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D736F82293A5C000951D30 /* AppDelegate.m */; }; 23 | 72D736FC2293A5C000951D30 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D736FB2293A5C000951D30 /* ViewController.m */; }; 24 | 72D736FF2293A5C000951D30 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 72D736FD2293A5C000951D30 /* Main.storyboard */; }; 25 | 72D737012293A5C300951D30 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 72D737002293A5C300951D30 /* Assets.xcassets */; }; 26 | 72D737042293A5C300951D30 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 72D737022293A5C300951D30 /* LaunchScreen.storyboard */; }; 27 | 72D737072293A5C300951D30 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D737062293A5C300951D30 /* main.m */; }; 28 | 72D7370F2293A61800951D30 /* PageController.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D7370E2293A61800951D30 /* PageController.m */; }; 29 | 72F4E34A22B77E7400F02EA8 /* WaterHorizontalCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 72F4E34822B77E7400F02EA8 /* WaterHorizontalCell.m */; }; 30 | 72F4E34B22B77E7400F02EA8 /* WaterHorizontalCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 72F4E34922B77E7400F02EA8 /* WaterHorizontalCell.xib */; }; 31 | 72FA2B6222BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 72FA2B5E22BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.m */; }; 32 | 72FA2B6322BB5A7C00A8DF1C /* XCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 72FA2B6122BB5A7C00A8DF1C /* XCollectionViewLayout.m */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 72437BC822B14F2E0082F81C /* LayoutController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LayoutController.h; sourceTree = ""; }; 37 | 72437BC922B14F2E0082F81C /* LayoutController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LayoutController.m; sourceTree = ""; }; 38 | 72437BCB22B150670082F81C /* LayoutControlCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LayoutControlCell.h; sourceTree = ""; }; 39 | 72437BCC22B150670082F81C /* LayoutControlCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LayoutControlCell.m; sourceTree = ""; }; 40 | 72437BCD22B150670082F81C /* LayoutControlCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayoutControlCell.xib; sourceTree = ""; }; 41 | 726C65FC22AF2FA800C64025 /* XC_DeviceInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XC_DeviceInfo.h; sourceTree = ""; }; 42 | 726C65FD22AF2FA800C64025 /* XC_DeviceInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XC_DeviceInfo.m; sourceTree = ""; }; 43 | 726C65FF22AF30B900C64025 /* SFHFKeychainUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFHFKeychainUtils.h; sourceTree = ""; }; 44 | 726C660022AF30BA00C64025 /* SFHFKeychainUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFHFKeychainUtils.m; sourceTree = ""; }; 45 | 726C660322AF364E00C64025 /* DeviceInfoDefine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceInfoDefine.h; sourceTree = ""; }; 46 | 726C660422AF583800C64025 /* cover.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = cover.jpg; sourceTree = ""; }; 47 | 726C660622AF592800C64025 /* UIShadow+UIView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIShadow+UIView.h"; sourceTree = ""; }; 48 | 726C660722AF592800C64025 /* UIShadow+UIView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIShadow+UIView.m"; sourceTree = ""; }; 49 | 726C660C22AF827400C64025 /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = ""; }; 50 | 72B4759622B6703E004670D8 /* WaterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WaterViewController.h; sourceTree = ""; }; 51 | 72B4759722B6703E004670D8 /* WaterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WaterViewController.m; sourceTree = ""; }; 52 | 72B4759A22B687FA004670D8 /* 1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1.png; sourceTree = ""; }; 53 | 72B4759B22B687FA004670D8 /* 4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 4.png; sourceTree = ""; }; 54 | 72B4759C22B687FA004670D8 /* 3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 3.png; sourceTree = ""; }; 55 | 72B4759D22B687FA004670D8 /* 2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 2.png; sourceTree = ""; }; 56 | 72D736F42293A5BF00951D30 /* PageTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PageTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 72D736F72293A5C000951D30 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 58 | 72D736F82293A5C000951D30 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 59 | 72D736FA2293A5C000951D30 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 60 | 72D736FB2293A5C000951D30 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 61 | 72D736FE2293A5C000951D30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 62 | 72D737002293A5C300951D30 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63 | 72D737032293A5C300951D30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 64 | 72D737052293A5C300951D30 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 72D737062293A5C300951D30 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 66 | 72D7370D2293A61800951D30 /* PageController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PageController.h; sourceTree = ""; }; 67 | 72D7370E2293A61800951D30 /* PageController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PageController.m; sourceTree = ""; }; 68 | 72F4E34722B77E7400F02EA8 /* WaterHorizontalCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WaterHorizontalCell.h; sourceTree = ""; }; 69 | 72F4E34822B77E7400F02EA8 /* WaterHorizontalCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WaterHorizontalCell.m; sourceTree = ""; }; 70 | 72F4E34922B77E7400F02EA8 /* WaterHorizontalCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WaterHorizontalCell.xib; sourceTree = ""; }; 71 | 72FA2B5E22BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCWaterCollectionViewLayout.m; sourceTree = ""; }; 72 | 72FA2B5F22BB5A7C00A8DF1C /* XCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCollectionViewLayout.h; sourceTree = ""; }; 73 | 72FA2B6022BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCWaterCollectionViewLayout.h; sourceTree = ""; }; 74 | 72FA2B6122BB5A7C00A8DF1C /* XCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCollectionViewLayout.m; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 72D736F12293A5BF00951D30 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 72437BC022B14DAE0082F81C /* UIShadow+UIView */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 726C660622AF592800C64025 /* UIShadow+UIView.h */, 92 | 726C660722AF592800C64025 /* UIShadow+UIView.m */, 93 | ); 94 | path = "UIShadow+UIView"; 95 | sourceTree = ""; 96 | }; 97 | 72437BD022B1506E0082F81C /* FlowLayoutTest */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 72437BC822B14F2E0082F81C /* LayoutController.h */, 101 | 72437BC922B14F2E0082F81C /* LayoutController.m */, 102 | 72437BCB22B150670082F81C /* LayoutControlCell.h */, 103 | 72437BCC22B150670082F81C /* LayoutControlCell.m */, 104 | 72437BCD22B150670082F81C /* LayoutControlCell.xib */, 105 | ); 106 | path = FlowLayoutTest; 107 | sourceTree = ""; 108 | }; 109 | 726C660222AF362A00C64025 /* DeviceInfo */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 726C660322AF364E00C64025 /* DeviceInfoDefine.h */, 113 | 726C65FF22AF30B900C64025 /* SFHFKeychainUtils.h */, 114 | 726C660022AF30BA00C64025 /* SFHFKeychainUtils.m */, 115 | 726C65FC22AF2FA800C64025 /* XC_DeviceInfo.h */, 116 | 726C65FD22AF2FA800C64025 /* XC_DeviceInfo.m */, 117 | ); 118 | path = DeviceInfo; 119 | sourceTree = ""; 120 | }; 121 | 72B4759522B67008004670D8 /* WaterLayoutTest */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 72B4759A22B687FA004670D8 /* 1.png */, 125 | 72B4759D22B687FA004670D8 /* 2.png */, 126 | 72B4759C22B687FA004670D8 /* 3.png */, 127 | 72B4759B22B687FA004670D8 /* 4.png */, 128 | 72B4759622B6703E004670D8 /* WaterViewController.h */, 129 | 72B4759722B6703E004670D8 /* WaterViewController.m */, 130 | 72F4E34722B77E7400F02EA8 /* WaterHorizontalCell.h */, 131 | 72F4E34822B77E7400F02EA8 /* WaterHorizontalCell.m */, 132 | 72F4E34922B77E7400F02EA8 /* WaterHorizontalCell.xib */, 133 | ); 134 | path = WaterLayoutTest; 135 | sourceTree = ""; 136 | }; 137 | 72D736EB2293A5BF00951D30 = { 138 | isa = PBXGroup; 139 | children = ( 140 | 72D736F62293A5C000951D30 /* PageTest */, 141 | 72D736F52293A5BF00951D30 /* Products */, 142 | ); 143 | sourceTree = ""; 144 | }; 145 | 72D736F52293A5BF00951D30 /* Products */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 72D736F42293A5BF00951D30 /* PageTest.app */, 149 | ); 150 | name = Products; 151 | sourceTree = ""; 152 | }; 153 | 72D736F62293A5C000951D30 /* PageTest */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 72FA2B5D22BB5A7C00A8DF1C /* XCollectionViewLayout */, 157 | 72437BC022B14DAE0082F81C /* UIShadow+UIView */, 158 | 72B4759522B67008004670D8 /* WaterLayoutTest */, 159 | 72437BD022B1506E0082F81C /* FlowLayoutTest */, 160 | 726C660222AF362A00C64025 /* DeviceInfo */, 161 | 72D736F72293A5C000951D30 /* AppDelegate.h */, 162 | 72D736F82293A5C000951D30 /* AppDelegate.m */, 163 | 72D736FA2293A5C000951D30 /* ViewController.h */, 164 | 72D736FB2293A5C000951D30 /* ViewController.m */, 165 | 726C660422AF583800C64025 /* cover.jpg */, 166 | 72D736FD2293A5C000951D30 /* Main.storyboard */, 167 | 72D737002293A5C300951D30 /* Assets.xcassets */, 168 | 72D737022293A5C300951D30 /* LaunchScreen.storyboard */, 169 | 72D737052293A5C300951D30 /* Info.plist */, 170 | 72D737062293A5C300951D30 /* main.m */, 171 | 72D7370D2293A61800951D30 /* PageController.h */, 172 | 72D7370E2293A61800951D30 /* PageController.m */, 173 | 726C660C22AF827400C64025 /* PrefixHeader.pch */, 174 | ); 175 | path = PageTest; 176 | sourceTree = ""; 177 | }; 178 | 72FA2B5D22BB5A7C00A8DF1C /* XCollectionViewLayout */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 72FA2B6022BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.h */, 182 | 72FA2B5E22BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.m */, 183 | 72FA2B5F22BB5A7C00A8DF1C /* XCollectionViewLayout.h */, 184 | 72FA2B6122BB5A7C00A8DF1C /* XCollectionViewLayout.m */, 185 | ); 186 | name = XCollectionViewLayout; 187 | path = ../../XCollectionViewLayout; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXGroup section */ 191 | 192 | /* Begin PBXNativeTarget section */ 193 | 72D736F32293A5BF00951D30 /* PageTest */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = 72D7370A2293A5C300951D30 /* Build configuration list for PBXNativeTarget "PageTest" */; 196 | buildPhases = ( 197 | 72D736F02293A5BF00951D30 /* Sources */, 198 | 72D736F12293A5BF00951D30 /* Frameworks */, 199 | 72D736F22293A5BF00951D30 /* Resources */, 200 | ); 201 | buildRules = ( 202 | ); 203 | dependencies = ( 204 | ); 205 | name = PageTest; 206 | productName = PageTest; 207 | productReference = 72D736F42293A5BF00951D30 /* PageTest.app */; 208 | productType = "com.apple.product-type.application"; 209 | }; 210 | /* End PBXNativeTarget section */ 211 | 212 | /* Begin PBXProject section */ 213 | 72D736EC2293A5BF00951D30 /* Project object */ = { 214 | isa = PBXProject; 215 | attributes = { 216 | LastUpgradeCheck = 1020; 217 | ORGANIZATIONNAME = xiaocan; 218 | TargetAttributes = { 219 | 72D736F32293A5BF00951D30 = { 220 | CreatedOnToolsVersion = 10.2.1; 221 | }; 222 | }; 223 | }; 224 | buildConfigurationList = 72D736EF2293A5BF00951D30 /* Build configuration list for PBXProject "PageTest" */; 225 | compatibilityVersion = "Xcode 9.3"; 226 | developmentRegion = en; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | Base, 231 | ); 232 | mainGroup = 72D736EB2293A5BF00951D30; 233 | productRefGroup = 72D736F52293A5BF00951D30 /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | 72D736F32293A5BF00951D30 /* PageTest */, 238 | ); 239 | }; 240 | /* End PBXProject section */ 241 | 242 | /* Begin PBXResourcesBuildPhase section */ 243 | 72D736F22293A5BF00951D30 /* Resources */ = { 244 | isa = PBXResourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 726C660522AF583900C64025 /* cover.jpg in Resources */, 248 | 72B4759F22B687FA004670D8 /* 4.png in Resources */, 249 | 72437BCF22B150670082F81C /* LayoutControlCell.xib in Resources */, 250 | 72B475A022B687FA004670D8 /* 3.png in Resources */, 251 | 72D737042293A5C300951D30 /* LaunchScreen.storyboard in Resources */, 252 | 72B4759E22B687FA004670D8 /* 1.png in Resources */, 253 | 72B475A122B687FA004670D8 /* 2.png in Resources */, 254 | 72F4E34B22B77E7400F02EA8 /* WaterHorizontalCell.xib in Resources */, 255 | 72D737012293A5C300951D30 /* Assets.xcassets in Resources */, 256 | 72D736FF2293A5C000951D30 /* Main.storyboard in Resources */, 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | }; 260 | /* End PBXResourcesBuildPhase section */ 261 | 262 | /* Begin PBXSourcesBuildPhase section */ 263 | 72D736F02293A5BF00951D30 /* Sources */ = { 264 | isa = PBXSourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 72D7370F2293A61800951D30 /* PageController.m in Sources */, 268 | 726C65FE22AF2FA800C64025 /* XC_DeviceInfo.m in Sources */, 269 | 72FA2B6322BB5A7C00A8DF1C /* XCollectionViewLayout.m in Sources */, 270 | 726C660122AF30BA00C64025 /* SFHFKeychainUtils.m in Sources */, 271 | 72437BCA22B14F2E0082F81C /* LayoutController.m in Sources */, 272 | 72F4E34A22B77E7400F02EA8 /* WaterHorizontalCell.m in Sources */, 273 | 726C660822AF592800C64025 /* UIShadow+UIView.m in Sources */, 274 | 72B4759822B6703E004670D8 /* WaterViewController.m in Sources */, 275 | 72FA2B6222BB5A7C00A8DF1C /* XCWaterCollectionViewLayout.m in Sources */, 276 | 72D736FC2293A5C000951D30 /* ViewController.m in Sources */, 277 | 72D737072293A5C300951D30 /* main.m in Sources */, 278 | 72437BCE22B150670082F81C /* LayoutControlCell.m in Sources */, 279 | 72D736F92293A5C000951D30 /* AppDelegate.m in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXVariantGroup section */ 286 | 72D736FD2293A5C000951D30 /* Main.storyboard */ = { 287 | isa = PBXVariantGroup; 288 | children = ( 289 | 72D736FE2293A5C000951D30 /* Base */, 290 | ); 291 | name = Main.storyboard; 292 | sourceTree = ""; 293 | }; 294 | 72D737022293A5C300951D30 /* LaunchScreen.storyboard */ = { 295 | isa = PBXVariantGroup; 296 | children = ( 297 | 72D737032293A5C300951D30 /* Base */, 298 | ); 299 | name = LaunchScreen.storyboard; 300 | sourceTree = ""; 301 | }; 302 | /* End PBXVariantGroup section */ 303 | 304 | /* Begin XCBuildConfiguration section */ 305 | 72D737082293A5C300951D30 /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_ENABLE_OBJC_WEAK = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 323 | CLANG_WARN_EMPTY_BODY = YES; 324 | CLANG_WARN_ENUM_CONVERSION = YES; 325 | CLANG_WARN_INFINITE_RECURSION = YES; 326 | CLANG_WARN_INT_CONVERSION = YES; 327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 329 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 330 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 331 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 332 | CLANG_WARN_STRICT_PROTOTYPES = YES; 333 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 334 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | CODE_SIGN_IDENTITY = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = dwarf; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | ENABLE_TESTABILITY = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu11; 343 | GCC_DYNAMIC_NO_PIC = NO; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 357 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 358 | MTL_FAST_MATH = YES; 359 | ONLY_ACTIVE_ARCH = YES; 360 | SDKROOT = iphoneos; 361 | }; 362 | name = Debug; 363 | }; 364 | 72D737092293A5C300951D30 /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_ANALYZER_NONNULL = YES; 369 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_ENABLE_OBJC_WEAK = YES; 375 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 376 | CLANG_WARN_BOOL_CONVERSION = YES; 377 | CLANG_WARN_COMMA = YES; 378 | CLANG_WARN_CONSTANT_CONVERSION = YES; 379 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INFINITE_RECURSION = YES; 385 | CLANG_WARN_INT_CONVERSION = YES; 386 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | CODE_SIGN_IDENTITY = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 399 | ENABLE_NS_ASSERTIONS = NO; 400 | ENABLE_STRICT_OBJC_MSGSEND = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu11; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 410 | MTL_ENABLE_DEBUG_INFO = NO; 411 | MTL_FAST_MATH = YES; 412 | SDKROOT = iphoneos; 413 | VALIDATE_PRODUCT = YES; 414 | }; 415 | name = Release; 416 | }; 417 | 72D7370B2293A5C300951D30 /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 421 | CODE_SIGN_STYLE = Automatic; 422 | DEVELOPMENT_TEAM = RDLPRV575D; 423 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 424 | GCC_PREFIX_HEADER = PageTest/PrefixHeader.pch; 425 | INFOPLIST_FILE = PageTest/Info.plist; 426 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 427 | LD_RUNPATH_SEARCH_PATHS = ( 428 | "$(inherited)", 429 | "@executable_path/Frameworks", 430 | ); 431 | PRODUCT_BUNDLE_IDENTIFIER = com.xiaocan.recorder.PageTest; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Debug; 436 | }; 437 | 72D7370C2293A5C300951D30 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 441 | CODE_SIGN_STYLE = Automatic; 442 | DEVELOPMENT_TEAM = RDLPRV575D; 443 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 444 | GCC_PREFIX_HEADER = PageTest/PrefixHeader.pch; 445 | INFOPLIST_FILE = PageTest/Info.plist; 446 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 447 | LD_RUNPATH_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "@executable_path/Frameworks", 450 | ); 451 | PRODUCT_BUNDLE_IDENTIFIER = com.xiaocan.recorder.PageTest; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | }; 455 | name = Release; 456 | }; 457 | /* End XCBuildConfiguration section */ 458 | 459 | /* Begin XCConfigurationList section */ 460 | 72D736EF2293A5BF00951D30 /* Build configuration list for PBXProject "PageTest" */ = { 461 | isa = XCConfigurationList; 462 | buildConfigurations = ( 463 | 72D737082293A5C300951D30 /* Debug */, 464 | 72D737092293A5C300951D30 /* Release */, 465 | ); 466 | defaultConfigurationIsVisible = 0; 467 | defaultConfigurationName = Release; 468 | }; 469 | 72D7370A2293A5C300951D30 /* Build configuration list for PBXNativeTarget "PageTest" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | 72D7370B2293A5C300951D30 /* Debug */, 473 | 72D7370C2293A5C300951D30 /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | /* End XCConfigurationList section */ 479 | }; 480 | rootObject = 72D736EC2293A5BF00951D30 /* Project object */; 481 | } 482 | -------------------------------------------------------------------------------- /PageTest/PageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PageTest/PageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PageTest/PageTest.xcodeproj/project.xcworkspace/xcuserdata/xiaocan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/PageTest/PageTest.xcodeproj/project.xcworkspace/xcuserdata/xiaocan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PageTest/PageTest.xcodeproj/xcuserdata/xiaocan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PageTest/PageTest.xcodeproj/xcuserdata/xiaocan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PageTest.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PageTest/PageTest/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /PageTest/PageTest/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 22 | 23 | 24 | 25 | UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController:[[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"viewControl"]]; 26 | self.window.rootViewController = navControl; 27 | [self.window makeKeyAndVisible]; 28 | return YES; 29 | } 30 | 31 | 32 | - (void)applicationWillResignActive:(UIApplication *)application { 33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 34 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 35 | } 36 | 37 | 38 | - (void)applicationDidEnterBackground:(UIApplication *)application { 39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application { 45 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 46 | } 47 | 48 | 49 | - (void)applicationDidBecomeActive:(UIApplication *)application { 50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 51 | } 52 | 53 | 54 | - (void)applicationWillTerminate:(UIApplication *)application { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /PageTest/PageTest/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 | } -------------------------------------------------------------------------------- /PageTest/PageTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PageTest/PageTest/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 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /PageTest/PageTest/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 | 35 | 43 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /PageTest/PageTest/DeviceInfo/DeviceInfoDefine.h: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceInfoDefine.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/11. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | 10 | #ifndef DeviceInfoDefine_h 11 | #define DeviceInfoDefine_h 12 | 13 | 14 | /** 唯一ID */ 15 | #define XC_UNIQUE_ID [XC_DeviceInfo getUniqueId] 16 | 17 | /** idfa */ 18 | #define XC_IDFA [XC_DeviceInfo idfaString] 19 | 20 | /** idfv */ 21 | #define XC_IDFV [XC_DeviceInfo idfvString] 22 | 23 | /** mac 地址 */ 24 | #define XC_MAC_ADDRESS [XC_DeviceInfo macAddress] 25 | 26 | /** ipv4 地址 */ 27 | #define XC_IPV4_ADDRESS [XC_DeviceInfo getIPAddress:YES] 28 | 29 | /** ipv6 地址 */ 30 | #define XC_IPV6_ADDRESS [XC_DeviceInfo getIPAddress:NO] 31 | 32 | /** 设备型号名称 如:iPhone10,1 */ 33 | #define XC_MODEL_NAME [XC_DeviceInfo modelName] 34 | 35 | /** 浏览器 User-Agent */ 36 | #define XC_USER_AGENT [XC_DeviceInfo userAgent] 37 | 38 | /** 设备宽高,如 750.0,1334.0 */ 39 | #define XC_SCREEN_SIZE [XC_DeviceInfo screenSize] 40 | 41 | /** 应用名称 */ 42 | #define XC_APP_NAME [XC_DeviceInfo appName] 43 | 44 | /** 应用包名 */ 45 | #define XC_APP_PACKAGE_NAME [XC_DeviceInfo appPackageName] 46 | 47 | /** bundle id */ 48 | #define XC_BUNDLE_ID [XC_DeviceInfo appBundleId] 49 | 50 | /** app version */ 51 | #define XC_APP_VERSION [XC_DeviceInfo appVersion] 52 | 53 | /** device version */ 54 | #define XC_DEVICE_VERSION [XC_DeviceInfo deviceVersion] 55 | 56 | /** 手机磁盘空间使用情况,如:6.8/64.0 */ 57 | #define XC_DISK_USAGE [XC_DeviceInfo diskUsage] 58 | 59 | /** 手机磁盘总空间,如:63989469184 (64G) */ 60 | #define XC_DEVICE_TOTAL_SPACE [XC_DeviceInfo deviceStorageSpace:YES] 61 | 62 | /** 手机磁盘剩余空间 */ 63 | #define XC_DEVICE_FREE_SPACE [XC_DeviceInfo deviceStorageSpace:NO] 64 | 65 | 66 | 67 | #endif /* DeviceInfoDefine_h */ 68 | 69 | 70 | 71 | 72 | 73 | //自定义log输出 74 | #ifdef DEBUG 75 | #define XC_LOG(param,...) NSLog((@"\n-------------- XC_LOG ↓ --------------\n" @"log info:" param @"\nlog at line: %d\n" @"log at file: %@\n" @"log at function: %@" @"\n-------------- XC_LOG ↑ --------------\n"),##__VA_ARGS__,__LINE__,[NSString stringWithUTF8String:__FILE__],[NSString stringWithUTF8String:__PRETTY_FUNCTION__]) 76 | #else 77 | #define XC_LOG(...) 78 | #endif 79 | -------------------------------------------------------------------------------- /PageTest/PageTest/DeviceInfo/SFHFKeychainUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFHFKeychainUtils.h 3 | // 4 | // Created by Buzz Andersen on 10/20/08. 5 | // Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. 6 | // Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | // Updated with ARC support. Originally forked from: 30 | // https://github.com/beelsebob/F1Pad/commit/66ea68e08eef0a66c706c45583e071f0a032f3c8 31 | 32 | #import 33 | 34 | 35 | @interface SFHFKeychainUtils : NSObject { 36 | 37 | } 38 | 39 | + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; 40 | + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error; 41 | + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; 42 | + (BOOL) purgeItemsForServiceName:(NSString *) serviceName error: (NSError **) error; 43 | 44 | @end -------------------------------------------------------------------------------- /PageTest/PageTest/DeviceInfo/SFHFKeychainUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFHFKeychainUtils.m 3 | // 4 | // Created by Buzz Andersen on 10/20/08. 5 | // Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. 6 | // Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import "SFHFKeychainUtils.h" 31 | #import 32 | 33 | static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain"; 34 | 35 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR 36 | @interface SFHFKeychainUtils (PrivateMethods) 37 | + (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; 38 | @end 39 | #endif 40 | 41 | @implementation SFHFKeychainUtils 42 | 43 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR 44 | 45 | + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { 46 | if (!username || !serviceName) { 47 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 48 | return nil; 49 | } 50 | 51 | SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error]; 52 | 53 | if (*error || !item) { 54 | return nil; 55 | } 56 | 57 | // from Advanced Mac OS X Programming, ch. 16 58 | UInt32 length; 59 | char *password; 60 | SecKeychainAttribute attributes[8]; 61 | SecKeychainAttributeList list; 62 | 63 | attributes[0].tag = kSecAccountItemAttr; 64 | attributes[1].tag = kSecDescriptionItemAttr; 65 | attributes[2].tag = kSecLabelItemAttr; 66 | attributes[3].tag = kSecModDateItemAttr; 67 | 68 | list.count = 4; 69 | list.attr = attributes; 70 | 71 | OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password); 72 | 73 | if (status != noErr) { 74 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 75 | return nil; 76 | } 77 | 78 | NSString *passwordString = nil; 79 | 80 | if (password != NULL) { 81 | char passwordBuffer[1024]; 82 | 83 | if (length > 1023) { 84 | length = 1023; 85 | } 86 | strncpy(passwordBuffer, password, length); 87 | 88 | passwordBuffer[length] = '\0'; 89 | passwordString = [NSString stringWithCString:passwordBuffer]; 90 | } 91 | 92 | SecKeychainItemFreeContent(&list, password); 93 | 94 | CFRelease(item); 95 | 96 | return passwordString; 97 | } 98 | 99 | + (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error { 100 | if (!username || !password || !serviceName) { 101 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 102 | return; 103 | } 104 | 105 | OSStatus status = noErr; 106 | 107 | SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error]; 108 | 109 | if (*error && [*error code] != noErr) { 110 | return; 111 | } 112 | 113 | *error = nil; 114 | 115 | if (item) { 116 | status = SecKeychainItemModifyAttributesAndData(item, 117 | NULL, 118 | strlen([password UTF8String]), 119 | [password UTF8String]); 120 | 121 | CFRelease(item); 122 | } 123 | else { 124 | status = SecKeychainAddGenericPassword(NULL, 125 | strlen([serviceName UTF8String]), 126 | [serviceName UTF8String], 127 | strlen([username UTF8String]), 128 | [username UTF8String], 129 | strlen([password UTF8String]), 130 | [password UTF8String], 131 | NULL); 132 | } 133 | 134 | if (status != noErr) { 135 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 136 | } 137 | } 138 | 139 | + (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { 140 | if (!username || !serviceName) { 141 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil]; 142 | return; 143 | } 144 | 145 | *error = nil; 146 | 147 | SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error]; 148 | 149 | if (*error && [*error code] != noErr) { 150 | return; 151 | } 152 | 153 | OSStatus status; 154 | 155 | if (item) { 156 | status = SecKeychainItemDelete(item); 157 | 158 | CFRelease(item); 159 | } 160 | 161 | if (status != noErr) { 162 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 163 | } 164 | } 165 | 166 | + (void) purgeItemsForServiceName:(NSString *) serviceName error: (NSError **) error { 167 | if (!serviceName) { 168 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil]; 169 | return; 170 | } 171 | 172 | NSMutableDictionary *searchData = [NSMutableDictionary new]; 173 | [searchData setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; 174 | [searchData setObject:serviceName forKey:(__bridge id)kSecAttrService]; 175 | 176 | OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef)searchData); 177 | 178 | if (error != nil && status != noErr) 179 | { 180 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 181 | } 182 | } 183 | 184 | + (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { 185 | if (!username || !serviceName) { 186 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 187 | return nil; 188 | } 189 | 190 | *error = nil; 191 | 192 | SecKeychainItemRef item; 193 | 194 | OSStatus status = SecKeychainFindGenericPassword(NULL, 195 | strlen([serviceName UTF8String]), 196 | [serviceName UTF8String], 197 | strlen([username UTF8String]), 198 | [username UTF8String], 199 | NULL, 200 | NULL, 201 | &item); 202 | 203 | if (status != noErr) { 204 | if (status != errSecItemNotFound) { 205 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 206 | } 207 | 208 | return nil; 209 | } 210 | 211 | return item; 212 | } 213 | 214 | #else 215 | 216 | + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { 217 | if (!username || !serviceName) { 218 | if (error != nil) { 219 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 220 | } 221 | return nil; 222 | } 223 | 224 | if (error != nil) { 225 | *error = nil; 226 | } 227 | 228 | // Set up a query dictionary with the base query attributes: item type (generic), username, and service 229 | 230 | NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil]; 231 | NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil]; 232 | 233 | NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys]; 234 | 235 | // First do a query for attributes, in case we already have a Keychain item with no password data set. 236 | // One likely way such an incorrect item could have come about is due to the previous (incorrect) 237 | // version of this code (which set the password as a generic attribute instead of password data). 238 | 239 | NSMutableDictionary *attributeQuery = [query mutableCopy]; 240 | [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes]; 241 | CFTypeRef attrResult = NULL; 242 | OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult); 243 | //NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult; 244 | 245 | if (status != noErr) { 246 | // No existing item found--simply return nil for the password 247 | if (error != nil && status != errSecItemNotFound) { 248 | //Only return an error if a real exception happened--not simply for "not found." 249 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 250 | } 251 | 252 | return nil; 253 | } 254 | 255 | // We have an existing item, now query for the password data associated with it. 256 | 257 | NSMutableDictionary *passwordQuery = [query mutableCopy]; 258 | [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData]; 259 | CFTypeRef resData = NULL; 260 | status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData); 261 | NSData *resultData = (__bridge_transfer NSData *)resData; 262 | 263 | if (status != noErr) { 264 | if (status == errSecItemNotFound) { 265 | // We found attributes for the item previously, but no password now, so return a special error. 266 | // Users of this API will probably want to detect this error and prompt the user to 267 | // re-enter their credentials. When you attempt to store the re-entered credentials 268 | // using storeUsername:andPassword:forServiceName:updateExisting:error 269 | // the old, incorrect entry will be deleted and a new one with a properly encrypted 270 | // password will be added. 271 | if (error != nil) { 272 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil]; 273 | } 274 | } 275 | else { 276 | // Something else went wrong. Simply return the normal Keychain API error code. 277 | if (error != nil) { 278 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 279 | } 280 | } 281 | 282 | return nil; 283 | } 284 | 285 | NSString *password = nil; 286 | 287 | if (resultData) { 288 | password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding]; 289 | } 290 | else { 291 | // There is an existing item, but we weren't able to get password data for it for some reason, 292 | // Possibly as a result of an item being incorrectly entered by the previous code. 293 | // Set the -1999 error so the code above us can prompt the user again. 294 | if (error != nil) { 295 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil]; 296 | } 297 | } 298 | 299 | return password; 300 | } 301 | 302 | + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error 303 | { 304 | if (!username || !password || !serviceName) 305 | { 306 | if (error != nil) 307 | { 308 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 309 | } 310 | return NO; 311 | } 312 | 313 | // See if we already have a password entered for these credentials. 314 | NSError *getError = nil; 315 | NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError]; 316 | 317 | if ([getError code] == -1999) 318 | { 319 | // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code. 320 | // Delete the existing item before moving on entering a correct one. 321 | 322 | getError = nil; 323 | 324 | [self deleteItemForUsername: username andServiceName: serviceName error: &getError]; 325 | 326 | if ([getError code] != noErr) 327 | { 328 | if (error != nil) 329 | { 330 | *error = getError; 331 | } 332 | return NO; 333 | } 334 | } 335 | else if ([getError code] != noErr) 336 | { 337 | if (error != nil) 338 | { 339 | *error = getError; 340 | } 341 | return NO; 342 | } 343 | 344 | if (error != nil) 345 | { 346 | *error = nil; 347 | } 348 | 349 | OSStatus status = noErr; 350 | 351 | if (existingPassword) 352 | { 353 | // We have an existing, properly entered item with a password. 354 | // Update the existing item. 355 | 356 | if (![existingPassword isEqualToString:password] && updateExisting) 357 | { 358 | //Only update if we're allowed to update existing. If not, simply do nothing. 359 | 360 | NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, 361 | kSecAttrService, 362 | kSecAttrLabel, 363 | kSecAttrAccount, 364 | nil]; 365 | 366 | NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, 367 | serviceName, 368 | serviceName, 369 | username, 370 | nil]; 371 | 372 | NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; 373 | 374 | status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]); 375 | } 376 | } 377 | else 378 | { 379 | // No existing entry (or an existing, improperly entered, and therefore now 380 | // deleted, entry). Create a new entry. 381 | 382 | NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, 383 | kSecAttrService, 384 | kSecAttrLabel, 385 | kSecAttrAccount, 386 | kSecValueData, 387 | nil]; 388 | 389 | NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, 390 | serviceName, 391 | serviceName, 392 | username, 393 | [password dataUsingEncoding: NSUTF8StringEncoding], 394 | nil]; 395 | 396 | NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; 397 | 398 | status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL); 399 | } 400 | 401 | if (error != nil && status != noErr) 402 | { 403 | // Something went wrong with adding the new item. Return the Keychain error code. 404 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 405 | 406 | return NO; 407 | } 408 | 409 | return YES; 410 | } 411 | 412 | + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error 413 | { 414 | if (!username || !serviceName) 415 | { 416 | if (error != nil) 417 | { 418 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 419 | } 420 | return NO; 421 | } 422 | 423 | if (error != nil) 424 | { 425 | *error = nil; 426 | } 427 | 428 | NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil]; 429 | NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil]; 430 | 431 | NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; 432 | 433 | OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query); 434 | 435 | if (error != nil && status != noErr) 436 | { 437 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 438 | 439 | return NO; 440 | } 441 | 442 | return YES; 443 | } 444 | 445 | + (BOOL) purgeItemsForServiceName:(NSString *) serviceName error: (NSError **) error { 446 | if (!serviceName) 447 | { 448 | if (error != nil) 449 | { 450 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; 451 | } 452 | return NO; 453 | } 454 | 455 | if (error != nil) 456 | { 457 | *error = nil; 458 | } 459 | 460 | NSMutableDictionary *searchData = [NSMutableDictionary new]; 461 | [searchData setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; 462 | [searchData setObject:serviceName forKey:(__bridge id)kSecAttrService]; 463 | 464 | OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef)searchData); 465 | 466 | if (error != nil && status != noErr) 467 | { 468 | *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; 469 | 470 | return NO; 471 | } 472 | 473 | return YES; 474 | } 475 | 476 | #endif 477 | 478 | @end -------------------------------------------------------------------------------- /PageTest/PageTest/DeviceInfo/XC_DeviceInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // XC_DeviceInfo.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/11. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface XC_DeviceInfo : NSObject 14 | 15 | /** 获取唯一标识 */ 16 | + (nonnull NSString *)getUniqueId; 17 | 18 | /** idfa 广告标识 */ 19 | + (nonnull NSString *)idfaString; 20 | 21 | 22 | /** idfv */ 23 | + (nonnull NSString *)idfvString; 24 | 25 | /** mac地址 */ 26 | + (nonnull NSString *)macAddress; 27 | 28 | /** 获取设备当前网络IP地址 */ 29 | + (nonnull NSString *)getIPAddress:(BOOL)preferIPv4; 30 | 31 | /** 设备机型名称 如:iPhone10,1 */ 32 | + (nonnull NSString *)modelName; 33 | 34 | /** 浏览器userAgent */ 35 | + (nonnull NSString *)userAgent; 36 | 37 | /** 屏幕尺⼨ */ 38 | + (nonnull NSString *)screenSize; 39 | 40 | /** app名 */ 41 | + (nonnull NSString *)appName; 42 | 43 | /** 应用包名 */ 44 | + (nonnull NSString *)appPackageName; 45 | 46 | /** 应用 bundleID */ 47 | + (nonnull NSString *)appBundleId; 48 | 49 | /** APP版本号 */ 50 | + (nonnull NSString *)appVersion; 51 | 52 | /** 设备系统版本 */ 53 | + (nonnull NSString *)deviceVersion; 54 | 55 | /** 磁盘使用情况 */ 56 | + (nonnull NSString *)diskUsage; 57 | 58 | /** 当前手机磁盘空间大小 ,totalSpace:YES:磁盘空间总大小,NO:磁盘剩余可用空间 */ 59 | + (long long)deviceStorageSpace:(BOOL)totalSpace; 60 | 61 | @end 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /PageTest/PageTest/DeviceInfo/XC_DeviceInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // XC_DeviceInfo.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/11. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "XC_DeviceInfo.h" 10 | 11 | #import "SFHFKeychainUtils.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #import 17 | 18 | #import 19 | #import 20 | #import 21 | 22 | #import 23 | 24 | #import "sys/utsname.h" 25 | #include // Per msqr 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | #define IOS_CELLULAR @"pdp_ip0" 32 | #define IOS_WIFI @"en0" 33 | //#define IOS_VPN @"utun0" 34 | #define IP_ADDR_IPv4 @"ipv4" 35 | #define IP_ADDR_IPv6 @"ipv6" 36 | 37 | 38 | NSString *const UUID_USER_NAME = @"com.xiaocan._uuid"; 39 | NSString *const UUID_SERVICE = @"com.xiaocan._uuid_service"; 40 | 41 | @implementation XC_DeviceInfo 42 | 43 | 44 | /** 获取唯一标识 */ 45 | + (nonnull NSString *)getUniqueId{ 46 | NSString * strUUID = (NSString *)[SFHFKeychainUtils getPasswordForUsername:UUID_USER_NAME andServiceName:UUID_SERVICE error:nil]; 47 | 48 | //首次执行该方法时,uuid为空 49 | if ([strUUID isEqualToString:@""] || !strUUID){ 50 | 51 | //生成一个uuid的方法 52 | CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); 53 | strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef)); 54 | 55 | //将该uuid保存到keychain 56 | [SFHFKeychainUtils storeUsername:UUID_USER_NAME andPassword:strUUID forServiceName:UUID_SERVICE updateExisting:YES error:nil]; 57 | 58 | } 59 | return strUUID; 60 | } 61 | 62 | /** idfa 广告标识 */ 63 | + (nonnull NSString *)idfaString{ 64 | return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; 65 | } 66 | 67 | 68 | /** idfv */ 69 | + (nonnull NSString *)idfvString{ 70 | return [[[UIDevice currentDevice] identifierForVendor] UUIDString]; 71 | } 72 | 73 | /** mac地址 */ 74 | + (nonnull NSString *)macAddress{ 75 | int mib[6]; 76 | size_t len; 77 | char *buf; 78 | unsigned char *ptr; 79 | struct if_msghdr *ifm; 80 | struct sockaddr_dl *sdl; 81 | 82 | mib[0] = CTL_NET; 83 | mib[1] = AF_ROUTE; 84 | mib[2] = 0; 85 | mib[3] = AF_LINK; 86 | mib[4] = NET_RT_IFLIST; 87 | 88 | if ((mib[5] = if_nametoindex("en0")) == 0) { 89 | printf("Error: if_nametoindex error/n"); 90 | return NULL; 91 | } 92 | 93 | if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { 94 | printf("Error: sysctl, take 1/n"); 95 | return NULL; 96 | } 97 | 98 | if ((buf = malloc(len)) == NULL) { 99 | printf("Could not allocate memory. error!/n"); 100 | return NULL; 101 | } 102 | 103 | if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { 104 | printf("Error: sysctl, take 2"); 105 | return NULL; 106 | } 107 | 108 | ifm = (struct if_msghdr *)buf; 109 | sdl = (struct sockaddr_dl *)(ifm + 1); 110 | ptr = (unsigned char *)LLADDR(sdl); 111 | 112 | NSString *outstring = [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)]; 113 | free(buf); 114 | 115 | return [outstring uppercaseString]; 116 | } 117 | 118 | /** 获取设备当前网络IP地址 */ 119 | + (nonnull NSString *)getIPAddress:(BOOL)preferIPv4{ 120 | NSArray *searchArray = preferIPv4 ? 121 | @[ /*IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6,*/ IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] : 122 | @[ /*IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4,*/ IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ; 123 | 124 | NSDictionary *addresses = [self getIPAddresses]; 125 | // NSLog(@"addresses: %@", addresses); 126 | 127 | __block NSString *address; 128 | [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) 129 | { 130 | address = addresses[key]; 131 | if(address) *stop = YES; 132 | } ]; 133 | return address ? address : @"0.0.0.0"; 134 | } 135 | 136 | //获取所有相关IP信息 137 | + (NSDictionary *)getIPAddresses{ 138 | NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; 139 | 140 | // retrieve the current interfaces - returns 0 on success 141 | struct ifaddrs *interfaces; 142 | if(!getifaddrs(&interfaces)) { 143 | // Loop through linked list of interfaces 144 | struct ifaddrs *interface; 145 | for(interface=interfaces; interface; interface=interface->ifa_next) { 146 | if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { 147 | continue; // deeply nested code harder to read 148 | } 149 | const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; 150 | char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; 151 | if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { 152 | NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; 153 | NSString *type; 154 | if(addr->sin_family == AF_INET) { 155 | if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { 156 | type = IP_ADDR_IPv4; 157 | } 158 | } else { 159 | const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; 160 | if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { 161 | type = IP_ADDR_IPv6; 162 | } 163 | } 164 | if(type) { 165 | NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; 166 | addresses[key] = [NSString stringWithUTF8String:addrBuf]; 167 | } 168 | } 169 | } 170 | // Free memory 171 | freeifaddrs(interfaces); 172 | } 173 | return [addresses count] ? addresses : nil; 174 | } 175 | 176 | /** 设备机型名称 */ 177 | + (nonnull NSString *)modelName{ 178 | struct utsname systemInfo; 179 | uname(&systemInfo); 180 | NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; 181 | return platform?platform:@"unknow"; 182 | } 183 | 184 | /** 浏览器userAgent */ 185 | + (nonnull NSString *)userAgent{ 186 | NSString *ua = [[NSUserDefaults standardUserDefaults] objectForKey:@"navigator.userAgent"]; 187 | if (!ua) { 188 | if ([NSThread isMainThread]) { 189 | UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero]; 190 | NSString *useAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; 191 | [[NSUserDefaults standardUserDefaults] setObject:useAgent forKey:@"navigator.userAgent"]; 192 | }else{ 193 | dispatch_sync(dispatch_get_main_queue(), ^{ 194 | UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero]; 195 | NSString *useAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; 196 | [[NSUserDefaults standardUserDefaults] setObject:useAgent forKey:@"navigator.userAgent"]; 197 | }); 198 | } 199 | } 200 | if (ua) { 201 | return ua; 202 | } 203 | return @""; 204 | } 205 | 206 | /** 屏幕尺⼨ */ 207 | + (nonnull NSString *)screenSize{ 208 | CGFloat width = [UIScreen mainScreen].bounds.size.width * [UIScreen mainScreen].scale; 209 | CGFloat height = [UIScreen mainScreen].bounds.size.height * [UIScreen mainScreen].scale; 210 | NSString *size = @""; 211 | if (width > height) { 212 | size = [NSString stringWithFormat:@"%.01f,%.01f",height,width]; 213 | }else{ 214 | size = [NSString stringWithFormat:@"%.01f,%.01f",width,height]; 215 | } 216 | return size; 217 | } 218 | 219 | /** app名 */ 220 | + (nonnull NSString *)appName{ 221 | NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; 222 | if (!appName) { 223 | appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; 224 | } 225 | if (!appName) { 226 | appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; 227 | } 228 | if (!appName) { 229 | appName = @""; 230 | } 231 | return appName; 232 | } 233 | 234 | /** 应用包名 */ 235 | + (nonnull NSString *)appPackageName{ 236 | NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; 237 | 238 | if (!appName) { 239 | appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; 240 | } 241 | if (!appName) { 242 | appName = @""; 243 | } 244 | return appName; 245 | } 246 | 247 | /** 应用 bundleID */ 248 | + (nonnull NSString *)appBundleId{ 249 | NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]; 250 | if (!bundleId) { 251 | bundleId = @""; 252 | } 253 | return bundleId; 254 | } 255 | 256 | /** APP版本号 */ 257 | + (nonnull NSString *)appVersion{ 258 | return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 259 | } 260 | 261 | /** 设备系统版本 */ 262 | + (nonnull NSString *)deviceVersion{ 263 | return [UIDevice currentDevice].systemVersion; 264 | } 265 | 266 | /** 磁盘使用情况 */ 267 | + (nonnull NSString *)diskUsage{ 268 | return [NSString stringWithFormat:@"%.01lf/%.01lf",[self deviceStorageSpace:NO] / 1000.0 / 1000.0 / 1000.0,[self deviceStorageSpace:YES] / 1000.0 / 1000.0 / 1000.0]; 269 | } 270 | 271 | /** 当前手机磁盘空间大小 ,totalSpace:YES:磁盘空间总大小,NO:磁盘剩余可用空间 */ 272 | + (long long)deviceStorageSpace:(BOOL)totalSpace{ 273 | //剩余空间 274 | NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES) objectAtIndex:0]; 275 | NSFileManager *fileManager = [NSFileManager defaultManager]; 276 | NSDictionary *fileSysAttributes = [fileManager attributesOfFileSystemForPath:path error:nil]; 277 | NSNumber *freeSpaces = (NSNumber *)[fileSysAttributes objectForKey:NSFileSystemFreeSize]; 278 | NSNumber *totalSpaces = (NSNumber *)[fileSysAttributes objectForKey:NSFileSystemSize]; 279 | if (totalSpace) { 280 | return totalSpaces.longLongValue; 281 | } 282 | return freeSpaces.longLongValue; 283 | } 284 | 285 | @end 286 | -------------------------------------------------------------------------------- /PageTest/PageTest/FlowLayoutTest/LayoutControlCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutControlCell.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/12. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LayoutControlCell : UICollectionViewCell 14 | 15 | @property (nonatomic, weak) IBOutlet UILabel *numlabel; 16 | @property (nonatomic, weak) IBOutlet UIImageView *lImgView; 17 | @property (nonatomic, weak) CALayer *shadowLayer; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /PageTest/PageTest/FlowLayoutTest/LayoutControlCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutControlCell.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/12. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "LayoutControlCell.h" 10 | 11 | @implementation LayoutControlCell 12 | 13 | - (void)awakeFromNib { 14 | [super awakeFromNib]; 15 | // Initialization code 16 | self.contentView.backgroundColor = [UIColor colorWithRed:random() % 256 / 255.0 green:random() % 256 / 255.0 blue:random() % 230 / 255.0 alpha:1.0]; 17 | 18 | self.contentView.clipsToBounds = YES; 19 | self.contentView.layer.cornerRadius = 8.0; 20 | 21 | CALayer *shadowLayer = [CALayer layer]; 22 | shadowLayer.cornerRadius = 8.0; 23 | shadowLayer.backgroundColor = [UIColor whiteColor].CGColor; 24 | shadowLayer.shadowColor = [UIColor lightGrayColor].CGColor; 25 | shadowLayer.shadowRadius = 3.0; 26 | shadowLayer.shadowOpacity = 1.0; 27 | shadowLayer.shadowOffset = CGSizeZero; 28 | shadowLayer.frame = self.contentView.frame; 29 | [self.layer insertSublayer:shadowLayer atIndex:0]; 30 | self.shadowLayer = shadowLayer; 31 | 32 | self.clipsToBounds = NO; 33 | } 34 | 35 | - (void)layoutSubviews{ 36 | [super layoutSubviews]; 37 | self.shadowLayer.frame = self.bounds; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /PageTest/PageTest/FlowLayoutTest/LayoutControlCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /PageTest/PageTest/FlowLayoutTest/LayoutController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutController.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/12. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LayoutController : UIViewController 14 | 15 | - (instancetype)initWithAffineStyle:(BOOL)affineTransform; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /PageTest/PageTest/FlowLayoutTest/LayoutController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutController.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/12. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "LayoutController.h" 10 | #import "XCollectionViewLayout.h" 11 | #import "LayoutControlCell.h" 12 | 13 | @interface LayoutController () 14 | 15 | @property (nonatomic, strong) UICollectionView *collectView; 16 | @property (nonatomic) BOOL affileTransformStyle; 17 | 18 | @end 19 | 20 | @implementation LayoutController 21 | 22 | - (instancetype)initWithAffineStyle:(BOOL)affineTransform{ 23 | self = [super init]; 24 | if (self) { 25 | self.affileTransformStyle = affineTransform; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | self.view.backgroundColor = [UIColor whiteColor]; 34 | 35 | self.automaticallyAdjustsScrollViewInsets = NO; 36 | 37 | if (self.affileTransformStyle) { 38 | [self setTitle:@"仿射放大"]; 39 | }else{ 40 | [self setTitle:@"线性放大"]; 41 | } 42 | 43 | XCollectionViewLayout *layout = [[XCollectionViewLayout alloc] init]; 44 | layout.transformStyle = self.affileTransformStyle?XCollectionViewLayout_AffineTranform:XCollectionViewLayout_LinerTransform; 45 | _collectView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; 46 | _collectView.backgroundColor = [UIColor whiteColor]; 47 | _collectView.delegate = self; 48 | _collectView.dataSource = self; 49 | [_collectView registerNib:[UINib nibWithNibName:@"LayoutControlCell" bundle:nil] forCellWithReuseIdentifier:NSStringFromClass([LayoutControlCell class])]; 50 | [self.view addSubview:_collectView]; 51 | } 52 | 53 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{ 54 | return CGSizeMake(CGRectGetWidth(collectionView.frame) * 0.7, CGRectGetHeight(collectionView.frame) * 0.2); 55 | } 56 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{ 57 | return UIEdgeInsetsMake(CGRectGetHeight(collectionView.frame) * 0.4, 0, CGRectGetHeight(collectionView.frame) * 0.4, 0); 58 | } 59 | 60 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{ 61 | return 1; 62 | } 63 | 64 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 65 | return 20; 66 | } 67 | 68 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 69 | LayoutControlCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([LayoutControlCell class]) forIndexPath:indexPath]; 70 | cell.numlabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.item]; 71 | 72 | return cell; 73 | } 74 | 75 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 76 | if ([collectionView.collectionViewLayout clickCellAtIndexPath:indexPath]) { 77 | NSLog(@"click cell at section: %ld at item: %ld",(long)indexPath.section,(long)indexPath.item); 78 | } 79 | } 80 | 81 | /* 82 | #pragma mark - Navigation 83 | 84 | // In a storyboard-based application, you will often want to do a little preparation before navigation 85 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 86 | // Get the new view controller using [segue destinationViewController]. 87 | // Pass the selected object to the new view controller. 88 | } 89 | */ 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /PageTest/PageTest/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | UIInterfaceOrientationPortrait 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /PageTest/PageTest/PageController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PageController.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface PageController : UIViewController 14 | 15 | @property (nonatomic, copy) NSString *page; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /PageTest/PageTest/PageController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PageController.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "PageController.h" 10 | 11 | @interface PageController () 12 | 13 | @property (nonatomic, strong) UILabel *pageLabel; 14 | 15 | @end 16 | 17 | @implementation PageController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view. 22 | 23 | self.view.backgroundColor = [UIColor colorWithRed:random() % 256 / 255.0 green:random() % 256 / 255.0 blue:random() % 256 / 255.0 alpha:1.0]; 24 | 25 | _pageLabel = [[UILabel alloc] init]; 26 | _pageLabel.font = [UIFont boldSystemFontOfSize:40]; 27 | _pageLabel.textAlignment = NSTextAlignmentCenter; 28 | _pageLabel.textColor = [UIColor redColor]; 29 | _pageLabel.frame = self.view.bounds; 30 | [self.view addSubview:_pageLabel]; 31 | if (_page) { 32 | _pageLabel.text = _page; 33 | } 34 | } 35 | 36 | 37 | - (void)setPage:(NSString *)page{ 38 | _page = [page copy]; 39 | if (_pageLabel) { 40 | _pageLabel.text = _page; 41 | } 42 | } 43 | 44 | /* 45 | #pragma mark - Navigation 46 | 47 | // In a storyboard-based application, you will often want to do a little preparation before navigation 48 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 49 | // Get the new view controller using [segue destinationViewController]. 50 | // Pass the selected object to the new view controller. 51 | } 52 | */ 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /PageTest/PageTest/PrefixHeader.pch: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // PrefixHeader.pch 4 | // PageTest 5 | // 6 | // Created by xiao can on 2019/6/11. 7 | // Copyright © 2019 xiaocan. All rights reserved. 8 | // 9 | 10 | #ifndef PrefixHeader_pch 11 | #define PrefixHeader_pch 12 | 13 | // Include any system framework and library headers here that should be included in all compilation units. 14 | // You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. 15 | 16 | #import "UIShadow+UIView.h" 17 | 18 | #endif /* PrefixHeader_pch */ 19 | -------------------------------------------------------------------------------- /PageTest/PageTest/UIShadow+UIView/UIShadow+UIView.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIShadow+UIView.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/11. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIView(UIShadow_UIImageView) 14 | 15 | @property (nonatomic, strong, nullable) CALayer *shadow_layer; 16 | 17 | @property (nonatomic, strong) UIColor *shadow_color;//设置阴影色 18 | @property (nonatomic, assign) CGSize shadow_offset;//设置阴影偏移量 19 | @property (nonatomic, assign) CGFloat shadow_radius;//设置阴影半径 20 | @property (nonatomic, assign) CGFloat shadow_corner_radius;//设置阴影圆角 21 | @property (nonatomic, assign) CGFloat shadow_opacity;//设置阴影透明度 22 | @property (nonatomic, assign) CGPathRef shadow_path; 23 | @property (nonatomic, getter=isShowShadowLayer) BOOL showShadowLayer; 24 | 25 | - (void)showDefaultShadow; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /PageTest/PageTest/UIShadow+UIView/UIShadow+UIView.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIShadow+UIView.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/11. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "UIShadow+UIView.h" 10 | #import 11 | 12 | //key 13 | static NSString *const XC_SHADOW_LAYER = @"xc_shadow_layer"; 14 | static NSString *const XC_SHADOW_RADIUS = @"xc_shadow_radius"; 15 | static NSString *const XC_SHADOW_CORNER_RADIUS = @"xc_shadow_corner_radius"; 16 | static NSString *const XC_SHADOW_COLOR = @"xc_shadow_color"; 17 | static NSString *const XC_SHADOW_OFFSET = @"xc_shadow_offset"; 18 | static NSString *const XC_SHADOW_PATH = @"xc_shadow_path"; 19 | static NSString *const XC_SHADOW_OPACITY = @"xc_shadow_opacity"; 20 | static NSString *const XC_SHADOW_SHOW = @"xc_shadow_show"; 21 | 22 | 23 | //default shadow value 24 | #define XC_RADIUS 3.0f 25 | #define XC_CORNER_RADIUS 6.0f 26 | #define XC_OPACITY 1.0f 27 | #define XC_OFFSET CGSizeZero 28 | #define XC_COLOR [UIColor redColor] 29 | 30 | 31 | 32 | @implementation UIView(UIShadow_UIImageView) 33 | 34 | static inline void shadow_image_exchangeMethod(Method m1,Method m2){ 35 | method_exchangeImplementations(m1, m2); 36 | } 37 | 38 | static inline BOOL shadow_addInstanceMethod(Class cls,SEL select){ 39 | return class_addMethod(cls, select, class_getMethodImplementation(cls, select), method_getTypeEncoding(class_getInstanceMethod(cls, select))); 40 | } 41 | 42 | static inline BOOL shadow_addClassMethod(Class cls,SEL select){ 43 | return class_addMethod(cls, select, class_getMethodImplementation(cls, select), method_getTypeEncoding(class_getClassMethod(cls, select))); 44 | } 45 | 46 | 47 | + (void)load{ 48 | Class class = [self class]; 49 | shadow_image_exchangeMethod(class_getInstanceMethod(class, @selector(initWithFrame:)), class_getInstanceMethod(class, @selector(shadow_initWithFrame:))); 50 | shadow_image_exchangeMethod(class_getInstanceMethod(class, @selector(layoutSubviews)), class_getInstanceMethod(class, @selector(shadow_layoutSubViews))); 51 | shadow_image_exchangeMethod(class_getInstanceMethod(class, @selector(setHidden:)), class_getInstanceMethod(class, @selector(shadow_setHidden:))); 52 | 53 | shadow_image_exchangeMethod(class_getInstanceMethod(class, @selector(didMoveToSuperview)), class_getInstanceMethod(class, @selector(shadow_didMoveToSuperview))); 54 | shadow_image_exchangeMethod(class_getInstanceMethod(class, @selector(removeFromSuperview)), class_getInstanceMethod(class, @selector(shadow_removeFromSuperview))); 55 | shadow_image_exchangeMethod(class_getInstanceMethod(class, NSSelectorFromString(@"dealloc")), class_getInstanceMethod(class, @selector(shadow_dealloc))); 56 | 57 | shadow_addInstanceMethod(class, @selector(showShadowLayer)); 58 | shadow_addInstanceMethod(class, @selector(removeShadowLayer)); 59 | shadow_addInstanceMethod(class, @selector(showDefaultShadow)); 60 | } 61 | 62 | 63 | #pragma mark- values 64 | - (void)setShadow_layer:(CALayer *)shadow_layer{ 65 | objc_setAssociatedObject(self, &XC_SHADOW_LAYER, shadow_layer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 66 | } 67 | 68 | - (CALayer *)shadow_layer{ 69 | return objc_getAssociatedObject(self, &XC_SHADOW_LAYER); 70 | } 71 | 72 | - (void)setShadow_color:(UIColor *)shadow_color{ 73 | self.shadow_layer.shadowColor = shadow_color.CGColor; 74 | objc_setAssociatedObject(self, &XC_SHADOW_COLOR, shadow_color, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 75 | } 76 | 77 | - (UIColor *)shadow_color{ 78 | return objc_getAssociatedObject(self, &XC_SHADOW_COLOR); 79 | } 80 | 81 | - (void)setShadow_path:(CGPathRef)shadow_path{ 82 | self.shadow_layer.shadowPath = shadow_path; 83 | objc_setAssociatedObject(self, &XC_SHADOW_PATH, (__bridge id _Nullable)(shadow_path), OBJC_ASSOCIATION_ASSIGN); 84 | } 85 | 86 | - (CGPathRef)shadow_path{ 87 | return (__bridge CGPathRef _Nullable)(objc_getAssociatedObject(self, &XC_SHADOW_PATH)); 88 | } 89 | 90 | - (void)setShadow_offset:(CGSize)shadow_offset{ 91 | self.shadow_layer.shadowOffset = shadow_offset; 92 | objc_setAssociatedObject(self, &XC_SHADOW_OFFSET, NSStringFromCGSize(CGSizeZero), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 93 | } 94 | 95 | - (CGSize)shadow_offset{ 96 | NSString *size = objc_getAssociatedObject(self, &XC_SHADOW_OFFSET); 97 | CGSize offset = CGSizeZero; 98 | if (size && [size isKindOfClass:[NSString class]]) { 99 | offset = CGSizeFromString(size); 100 | } 101 | return offset; 102 | } 103 | 104 | - (void)setShadow_radius:(CGFloat)shadow_radius{ 105 | self.shadow_layer.shadowRadius = shadow_radius; 106 | objc_setAssociatedObject(self, &XC_SHADOW_OFFSET, [NSNumber numberWithFloat:shadow_radius], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 107 | } 108 | 109 | - (CGFloat)shadow_radius{ 110 | return [objc_getAssociatedObject(self, &XC_SHADOW_OFFSET) floatValue]; 111 | } 112 | 113 | - (void)setShadow_opacity:(CGFloat)shadow_opacity{ 114 | self.shadow_layer.shadowOpacity = shadow_opacity; 115 | objc_setAssociatedObject(self, &XC_SHADOW_OPACITY, [NSNumber numberWithFloat:shadow_opacity], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 116 | } 117 | 118 | - (CGFloat)shadow_opacity{ 119 | return [objc_getAssociatedObject(self, &XC_SHADOW_OPACITY) floatValue]; 120 | } 121 | 122 | - (void)setShadow_corner_radius:(CGFloat)shadow_corner_radius{ 123 | self.layer.masksToBounds = YES; 124 | self.layer.cornerRadius = shadow_corner_radius; 125 | self.shadow_layer.cornerRadius = shadow_corner_radius; 126 | objc_setAssociatedObject(self, &XC_SHADOW_CORNER_RADIUS, [NSNumber numberWithFloat:shadow_corner_radius], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 127 | } 128 | 129 | - (CGFloat)shadow_corner_radius{ 130 | return [objc_getAssociatedObject(self, &XC_SHADOW_CORNER_RADIUS) floatValue]; 131 | } 132 | 133 | - (void)setShowShadowLayer:(BOOL)showShadowLayer{ 134 | objc_setAssociatedObject(self, &XC_SHADOW_SHOW, [NSNumber numberWithBool:showShadowLayer], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 135 | showShadowLayer?[self showShadowLayer]:[self removeShadowLayer]; 136 | } 137 | 138 | #pragma mark- functions 139 | 140 | - (BOOL)isShowShadowLayer{ 141 | return [objc_getAssociatedObject(self, &XC_SHADOW_SHOW) boolValue]; 142 | } 143 | 144 | - (void)shadow_setHidden:(BOOL)hid{ 145 | [self shadow_setHidden:hid]; 146 | self.shadow_layer.hidden = hid; 147 | } 148 | 149 | - (void)shadow_layoutSubViews{ 150 | [self shadow_layoutSubViews]; 151 | [self shadow_layer_valchanged]; 152 | } 153 | 154 | - (void)shadow_didMoveToSuperview{ 155 | [self shadow_didMoveToSuperview]; 156 | if ([self isShowShadowLayer]) { 157 | [self showShadowLayer]; 158 | }else{ 159 | [self removeShadowLayer]; 160 | } 161 | } 162 | 163 | - (void)shadow_dealloc{ 164 | if (self.shadow_layer) { 165 | [self.shadow_layer removeFromSuperlayer]; 166 | self.shadow_layer = nil; 167 | } 168 | [self shadow_dealloc]; 169 | } 170 | 171 | - (void)shadow_removeFromSuperview{ 172 | if (self.shadow_layer) { 173 | [self.shadow_layer removeFromSuperlayer]; 174 | } 175 | [self shadow_removeFromSuperview]; 176 | } 177 | 178 | 179 | - (instancetype)shadow_initWithFrame:(CGRect)frame{ 180 | 181 | //init shadow_layer 182 | self.shadow_layer = [CALayer layer]; 183 | self.shadow_layer.backgroundColor = [UIColor whiteColor].CGColor; 184 | self.shadow_layer.frame = frame; 185 | 186 | //set default value 187 | self.shadow_color = [UIColor clearColor]; 188 | self.shadow_opacity = 0.0f; 189 | self.shadow_offset = CGSizeZero; 190 | self.shadow_radius = 0.0f; 191 | self.shadow_corner_radius = 0.0f; 192 | 193 | return [self shadow_initWithFrame:frame]; 194 | 195 | } 196 | 197 | //set default shadow 198 | - (void)showDefaultShadow{ 199 | //set default value 200 | self.shadow_color = XC_COLOR; 201 | self.shadow_opacity = XC_OPACITY; 202 | self.shadow_offset = XC_OFFSET; 203 | self.shadow_radius = XC_RADIUS; 204 | self.shadow_corner_radius = XC_CORNER_RADIUS; 205 | 206 | self.shadow_layer.frame = self.frame; 207 | self.showShadowLayer = YES; 208 | } 209 | 210 | 211 | //show shadow 212 | - (void)showShadowLayer{ 213 | self.shadow_layer.frame = self.frame; 214 | [self.shadow_layer removeFromSuperlayer]; 215 | if (self.superview) { 216 | [self.superview.layer insertSublayer:self.shadow_layer below:self.layer]; 217 | } 218 | } 219 | 220 | //remove shadow 221 | - (void)removeShadowLayer{ 222 | if (self.shadow_layer) { 223 | [self.shadow_layer removeFromSuperlayer]; 224 | } 225 | } 226 | 227 | - (void)shadow_layer_valchanged{ 228 | if (self.shadow_layer) { 229 | self.shadow_layer.frame = self.frame; 230 | } 231 | } 232 | 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /PageTest/PageTest/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /PageTest/PageTest/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PageController.h" 11 | #import "XC_DeviceInfo.h" 12 | #import "DeviceInfo/DeviceInfoDefine.h" 13 | #import "LayoutController.h" 14 | #import "WaterViewController.h" 15 | 16 | @interface ViewController () 17 | 18 | @property (nonatomic, strong) UIImageView *coverImgView; 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | // Do any additional setup after loading the view. 27 | } 28 | 29 | - (IBAction)jumpToControl:(UIButton *)sender{ 30 | if (sender.tag == 1) { 31 | WaterViewController *control = [[WaterViewController alloc] init]; 32 | [self.navigationController pushViewController:control animated:YES]; 33 | }else if (sender.tag == 2) { 34 | LayoutController *control = [[LayoutController alloc] initWithAffineStyle:YES]; 35 | [self.navigationController pushViewController:control animated:YES]; 36 | }else if (sender.tag == 3) { 37 | LayoutController *control = [[LayoutController alloc] init]; 38 | [self.navigationController pushViewController:control animated:YES]; 39 | } 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/PageTest/PageTest/WaterLayoutTest/1.png -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/PageTest/PageTest/WaterLayoutTest/2.png -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/PageTest/PageTest/WaterLayoutTest/3.png -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/PageTest/PageTest/WaterLayoutTest/4.png -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/WaterHorizontalCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // WaterHorizontalCell.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/17. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface WaterHorizontalCell : UICollectionViewCell 14 | 15 | @property (nonatomic, weak) IBOutlet UIImageView *coverImgView; 16 | @property (nonatomic, weak) IBOutlet UILabel *coverTitle; 17 | @property (nonatomic, weak) CALayer *shadowLayer; 18 | 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/WaterHorizontalCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // WaterHorizontalCell.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/17. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "WaterHorizontalCell.h" 10 | 11 | @implementation WaterHorizontalCell 12 | 13 | - (void)awakeFromNib { 14 | [super awakeFromNib]; 15 | // Initialization code 16 | 17 | self.contentView.clipsToBounds = YES; 18 | self.contentView.layer.cornerRadius = 8.0; 19 | 20 | CALayer *shadowLayer = [CALayer layer]; 21 | shadowLayer.cornerRadius = 8.0; 22 | shadowLayer.backgroundColor = [UIColor whiteColor].CGColor; 23 | shadowLayer.shadowColor = [UIColor lightGrayColor].CGColor; 24 | shadowLayer.shadowRadius = 3.0; 25 | shadowLayer.shadowOpacity = 1.0; 26 | shadowLayer.shadowOffset = CGSizeZero; 27 | shadowLayer.frame = self.contentView.frame; 28 | [self.layer insertSublayer:shadowLayer atIndex:0]; 29 | self.shadowLayer = shadowLayer; 30 | 31 | self.clipsToBounds = NO; 32 | } 33 | 34 | - (void)layoutSubviews{ 35 | [super layoutSubviews]; 36 | self.shadowLayer.frame = self.bounds; 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/WaterHorizontalCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/WaterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WaterViewController.h 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/16. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface WaterViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /PageTest/PageTest/WaterLayoutTest/WaterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WaterViewController.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/6/16. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "WaterViewController.h" 10 | #import "XCWaterCollectionViewLayout.h" 11 | #import "WaterHorizontalCell.h" 12 | 13 | @interface WaterViewController () 14 | 15 | @property (nonatomic, strong) UICollectionView *collectView; 16 | @property (nonatomic, strong) NSMutableArray *sections; 17 | 18 | @end 19 | 20 | @implementation WaterViewController 21 | 22 | - (NSMutableArray *)sections{ 23 | if (!_sections) { 24 | _sections = [NSMutableArray arrayWithCapacity:0]; 25 | } 26 | return _sections; 27 | } 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | // Do any additional setup after loading the view. 32 | self.view.backgroundColor = [UIColor whiteColor]; 33 | 34 | [self setTitle:@"瀑布流"]; 35 | 36 | XCWaterCollectionViewLayout *layout = [[XCWaterCollectionViewLayout alloc] init]; 37 | layout.scrollDirection = XCWaterCollectionViewScrollDirectionVertical; 38 | _collectView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.navigationController.navigationBar.frame), CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) - CGRectGetMaxY(self.navigationController.navigationBar.frame)) collectionViewLayout:layout]; 39 | _collectView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 40 | _collectView.backgroundColor = [UIColor whiteColor]; 41 | _collectView.delegate = self; 42 | _collectView.dataSource = self; 43 | [_collectView registerNib:[UINib nibWithNibName:@"WaterHorizontalCell" bundle:nil] forCellWithReuseIdentifier:NSStringFromClass([WaterHorizontalCell class])]; 44 | [_collectView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"]; 45 | [_collectView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer"]; 46 | [self.view addSubview:_collectView]; 47 | 48 | UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 49 | btn.backgroundColor = [UIColor redColor]; 50 | btn.clipsToBounds = YES; 51 | btn.layer.cornerRadius = 8.0; 52 | btn.frame = CGRectMake(CGRectGetWidth(self.view.frame) - 100.0 - 15.0, CGRectGetHeight(self.view.frame) - 30 - 40.0, 100, 40); 53 | [btn setTitle:@"增加数据" forState:UIControlStateNormal]; 54 | btn.titleLabel.font = [UIFont fontWithName:@"Marker Felt" size:15]; 55 | [self.view addSubview:btn]; 56 | [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside]; 57 | 58 | } 59 | 60 | - (void)btnClick:(UIButton *)sender{ 61 | NSMutableArray *datas = [NSMutableArray arrayWithCapacity:0]; 62 | for (int i = 0; i < 10; i++) { 63 | [datas addObject:@([@[@(25),@(40),@(55),@(70),@(85)][rand() % 5] floatValue] + 140)]; 64 | } 65 | [self.sections addObject:datas]; 66 | // [_collectView reloadData]; 67 | [_collectView insertSections:[NSIndexSet indexSetWithIndex:self.sections.count - 1]]; 68 | } 69 | 70 | - (NSInteger)itemColumns{ 71 | return 2; 72 | } 73 | 74 | - (CGFloat)itemSpaceAtSectioin:(NSInteger)section{ 75 | return 20.0; 76 | } 77 | - (CGFloat)itemLineSpaceAtSection:(NSInteger)section{ 78 | return 15.0; 79 | } 80 | 81 | - (UIEdgeInsets)sectionInsetsAtSection:(NSInteger)section{ 82 | return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0); 83 | } 84 | 85 | - (CGFloat)itemHeightAtIndexPath:(NSIndexPath *)indexPath{ 86 | NSArray *datas = [self.sections objectAtIndex:indexPath.section]; 87 | return [datas[indexPath.item] floatValue]; 88 | } 89 | - (CGSize)headerViewSizeAtSection:(NSInteger)section{ 90 | 91 | return CGSizeMake(CGRectGetWidth(self.view.frame), 50.0); 92 | } 93 | - (CGSize)footerViewSizeAtSection:(NSInteger)section{ 94 | return CGSizeZero; 95 | } 96 | 97 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{ 98 | return self.sections.count; 99 | } 100 | 101 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 102 | NSArray *datas = [self.sections objectAtIndex:section]; 103 | return datas.count; 104 | } 105 | 106 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{ 107 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:([kind isEqualToString:UICollectionElementKindSectionHeader]?@"header":@"footer") forIndexPath:indexPath]; 108 | UILabel *label = [view viewWithTag:101]; 109 | if (!label) { 110 | label = [[UILabel alloc] init]; 111 | label.font = [UIFont fontWithName:@"Marker Felt" size:20]; 112 | label.textColor = [UIColor blackColor]; 113 | label.tag = 101; 114 | label.numberOfLines = 0; 115 | label.frame = view.bounds; 116 | [view addSubview:label]; 117 | } 118 | label.textAlignment = NSTextAlignmentLeft; 119 | label.text = [NSString stringWithFormat:@"  section %ld",(long)indexPath.section]; 120 | view.backgroundColor = [UIColor lightGrayColor]; 121 | return view; 122 | } 123 | 124 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 125 | WaterHorizontalCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([WaterHorizontalCell class]) forIndexPath:indexPath]; 126 | cell.coverTitle.text = [NSString stringWithFormat:@"%ld-%ld",(long)indexPath.section,(long)indexPath.item]; 127 | cell.coverImgView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%d",rand() % 4 + 1]]; 128 | return cell; 129 | } 130 | 131 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 132 | NSLog(@"click cell at section: %ld at item: %ld",(long)indexPath.section,(long)indexPath.item); 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /PageTest/PageTest/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/PageTest/PageTest/cover.jpg -------------------------------------------------------------------------------- /PageTest/PageTest/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PageTest 4 | // 5 | // Created by xiao can on 2019/5/21. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XCCollectionViewLayout 2 | - UICollectionView瀑布流效果、滚动放大效果,支持水平、垂直滚动 3 | - [https://www.jianshu.com/p/6fc45587f91b](https://www.jianshu.com/p/6fc45587f91b) 4 | 5 | 6 | #### 使用pod导入文件或下载解压后将XCollectionViewLayout文件夹导入项目中 7 | > pod 'XCollectionViewLayout' 8 | 9 | ##### 导入头文件 10 | ```Object-C 11 | //瀑布流样式 UICollectionViewLayout 12 | #import "XCWaterCollectionViewLayout.h" 13 | 14 | //滑动放大、仿射放大样式 UICollectionViewLayout 15 | #import "XCollectionViewLayout.h" 16 | 17 | ``` 18 | 19 | 20 | 21 | |样式|效果图| 22 | |:-:|:-:| 23 | |滚动仿射放大效果|滚动仿射放大效果| 24 | |滚动放大效果|滚动放大效果| 25 | |瀑布流-垂直滚动|垂直滚动| 26 | |瀑布流-水平滚动|水平滚动| 27 | 28 | 29 |

导入XCollectionViewLayout.h

30 | XCollectionViewLayout属性及代理 31 | 32 | ```Object-C 33 | typedef NS_ENUM(NSInteger,XCollectionViewLayoutTransformStyle){ 34 | XCollectionViewLayout_LinerTransform,//线性放大 35 | XCollectionViewLayout_AffineTranform,//仿射旋转放大 36 | }; 37 | 38 | //滚动放大模式(只支持横向滚动) 39 | @interface XCollectionViewLayout : UICollectionViewFlowLayout 40 | 41 | @property (nonatomic) XCollectionViewLayoutTransformStyle transformStyle; 42 | @property (nonatomic) CGFloat scaleCoefficient;//缩小系数,default 0.8,建议0.6~0.8 43 | 44 | @end 45 | ``` 46 | 47 | 48 |

滚动仿射放大效果

49 | 50 | ```Object-C 51 | XCollectionViewLayout *layout = [[XCollectionViewLayout alloc] init]; 52 | layout.transformStyle = XCollectionViewLayout_AffineTranform; 53 | _collectView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; 54 | _collectView.backgroundColor = [UIColor whiteColor]; 55 | _collectView.delegate = self; 56 | _collectView.dataSource = self; 57 | [_collectView registerNib:[UINib nibWithNibName:@"LayoutControlCell" bundle:nil] forCellWithReuseIdentifier:NSStringFromClass([LayoutControlCell class])]; 58 | [self.view addSubview:_collectView]; 59 | ``` 60 | 61 |

滚动放大效果

62 | 63 | ```Object-C 64 | XCollectionViewLayout *layout = [[XCollectionViewLayout alloc] init]; 65 | layout.transformStyle = XCollectionViewLayout_LinerTransform; 66 | _collectView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; 67 | _collectView.backgroundColor = [UIColor whiteColor]; 68 | _collectView.delegate = self; 69 | _collectView.dataSource = self; 70 | [_collectView registerNib:[UINib nibWithNibName:@"LayoutControlCell" bundle:nil] forCellWithReuseIdentifier:NSStringFromClass([LayoutControlCell class])]; 71 | [self.view addSubview:_collectView]; 72 | ``` 73 | 74 | 75 | 76 |

瀑布流效果

77 |

导入XCWaterCollectionViewLayout.h

78 | 79 | ```Object-C 80 | XCTransformFlowLayout *layout = [[XCTransformFlowLayout alloc] init]; 81 | _collectView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; 82 | _collectView.backgroundColor = [UIColor lightGrayColor]; 83 | _collectView.delegate = self; 84 | _collectView.dataSource = self; 85 | [_collectView registerNib:[UINib nibWithNibName:@"LayoutControlCell" bundle:nil] forCellWithReuseIdentifier:NSStringFromClass([LayoutControlCell class])]; 86 | [self.view addSubview:_collectView]; 87 | ``` 88 | 89 | ```Object-C 90 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{ 91 | return CGSizeMake(CGRectGetWidth(collectionView.frame) * 0.7, CGRectGetHeight(collectionView.frame) * 0.2); 92 | } 93 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{ 94 | return UIEdgeInsetsMake(CGRectGetHeight(collectionView.frame) * 0.4, 0, CGRectGetHeight(collectionView.frame) * 0.4, 0); 95 | } 96 | 97 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{ 98 | return 1; 99 | } 100 | 101 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 102 | return 20; 103 | } 104 | 105 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 106 | LayoutControlCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([LayoutControlCell class]) forIndexPath:indexPath]; 107 | cell.numlabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.item]; 108 | 109 | return cell; 110 | } 111 | ``` 112 | 113 | XCWaterCollectionViewLayout属性及代理 114 | 115 | ```Object-C 116 | 117 | @protocol XCWaterCollectionViewLayoutDelegate 118 | 119 | /** 列数 */ 120 | - (NSInteger)itemColumns; 121 | 122 | /** item与item之间 水平间距 */ 123 | - (CGFloat)itemSpaceAtSectioin:(NSInteger)section; 124 | 125 | /** item与item之间 垂直间距 */ 126 | - (CGFloat)itemLineSpaceAtSection:(NSInteger)section; 127 | 128 | - (UIEdgeInsets)sectionInsetsAtSection:(NSInteger)section; 129 | 130 | /** item 高度,XCWaterCollectionViewScrollDirectionVertical模式下返回item宽度 */ 131 | - (CGFloat)itemHeightAtIndexPath:(NSIndexPath *)indexPath; 132 | 133 | /** header view */ 134 | - (CGSize)headerViewSizeAtSection:(NSInteger)section; 135 | 136 | /** footer view */ 137 | - (CGSize)footerViewSizeAtSection:(NSInteger)section; 138 | 139 | 140 | @end 141 | 142 | @interface XCWaterCollectionViewLayout : UICollectionViewLayout 143 | 144 | @property (nonatomic) NSInteger itemColumns;/**< default 2 */ 145 | @property (nonatomic) CGFloat itemSpace;/**< itemSpaceAtSectioin: */ 146 | @property (nonatomic) CGFloat lineSpace;/**< itemLineSpaceAtSection: */ 147 | @property (nonatomic) CGSize headerViewSize;/**< headerViewSizeAtSection: */ 148 | @property (nonatomic) CGSize footerSize;/**< footerViewSizeAtSection: */ 149 | @property (nonatomic) UIEdgeInsets sectioinInsets;/**< sectionInsetsAtSection: */ 150 | @property (nonatomic) CGFloat itemWidthOrHeight;/**< itemHeightAtIndexPath */ 151 | 152 | @property (nonatomic) XCWaterCollectionViewScrollDirection scrollDirection;//default XCWaterCollectionViewScrollDirectionVertical 153 | 154 | @end 155 | 156 | ``` 157 | 158 | -------------------------------------------------------------------------------- /Resources/affine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/Resources/affine.gif -------------------------------------------------------------------------------- /Resources/all.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/Resources/all.gif -------------------------------------------------------------------------------- /Resources/horizontal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/Resources/horizontal.gif -------------------------------------------------------------------------------- /Resources/lineLayout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/Resources/lineLayout.gif -------------------------------------------------------------------------------- /Resources/vertical.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaocan/XCCollectionViewLayout/3c5615eaf04bdd04f449921ac55d78faa8513d54/Resources/vertical.gif -------------------------------------------------------------------------------- /XCollectionViewLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "XCollectionViewLayout" 3 | s.version = "1.0" 4 | s.license = { :type => "MIT", :file => "LICENSE" } 5 | s.platform = :ios, "8.0" 6 | s.summary = "XCollectionViewLayout" 7 | s.homepage = "https://github.com/wangxiaocan/XCCollectionViewLayout" 8 | s.authors = { "xiaocan" => "1217272889@qq.com" } 9 | s.source = { :git => "https://github.com/wangxiaocan/XCCollectionViewLayout.git", :tag => "#{s.version}" } 10 | s.requires_arc = true 11 | s.source_files = "XCollectionViewLayout/*.{h,m}" 12 | s.public_header_files = "XCollectionViewLayout/*.h" 13 | s.frameworks = "UIKit" 14 | end 15 | -------------------------------------------------------------------------------- /XCollectionViewLayout/XCWaterCollectionViewLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCWaterCollectionViewLayout.h 3 | // PageTest 4 | // 5 | // Created by 王文科 on 2019/6/15. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSInteger, XCWaterCollectionViewScrollDirection) { 14 | XCWaterCollectionViewScrollDirectionVertical,//垂直方向滚动 15 | XCWaterCollectionViewScrollDirectionHorizontal//水平方向滚动 16 | }; 17 | 18 | @protocol XCWaterCollectionViewLayoutDelegate 19 | 20 | /** 列数 */ 21 | - (NSInteger)itemColumns; 22 | 23 | /** item与item之间 水平间距 */ 24 | - (CGFloat)itemSpaceAtSectioin:(NSInteger)section; 25 | 26 | /** item与item之间 垂直间距 */ 27 | - (CGFloat)itemLineSpaceAtSection:(NSInteger)section; 28 | 29 | - (UIEdgeInsets)sectionInsetsAtSection:(NSInteger)section; 30 | 31 | /** item 高度,XCWaterCollectionViewScrollDirectionVertical模式下返回item宽度 */ 32 | - (CGFloat)itemHeightAtIndexPath:(NSIndexPath *)indexPath; 33 | 34 | /** header view */ 35 | - (CGSize)headerViewSizeAtSection:(NSInteger)section; 36 | 37 | /** footer view */ 38 | - (CGSize)footerViewSizeAtSection:(NSInteger)section; 39 | 40 | 41 | @end 42 | 43 | /** 瀑布流 */ 44 | @interface XCWaterCollectionViewLayout : UICollectionViewLayout 45 | 46 | @property (nonatomic) NSInteger itemColumns;/**< default 2 */ 47 | @property (nonatomic) CGFloat itemSpace;/**< itemSpaceAtSectioin: */ 48 | @property (nonatomic) CGFloat lineSpace;/**< itemLineSpaceAtSection: */ 49 | @property (nonatomic) CGSize headerViewSize;/**< headerViewSizeAtSection: */ 50 | @property (nonatomic) CGSize footerSize;/**< footerViewSizeAtSection: */ 51 | @property (nonatomic) UIEdgeInsets sectioinInsets;/**< sectionInsetsAtSection: */ 52 | @property (nonatomic) CGFloat itemWidthOrHeight;/**< itemHeightAtIndexPath */ 53 | 54 | @property (nonatomic) XCWaterCollectionViewScrollDirection scrollDirection;//default XCWaterCollectionViewScrollDirectionVertical 55 | 56 | @end 57 | 58 | NS_ASSUME_NONNULL_END 59 | -------------------------------------------------------------------------------- /XCollectionViewLayout/XCWaterCollectionViewLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCWaterCollectionViewLayout.m 3 | // PageTest 4 | // 5 | // Created by 王文科 on 2019/6/15. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "XCWaterCollectionViewLayout.h" 10 | 11 | @interface XCWaterCollectionViewLayout() 12 | 13 | 14 | @property (nonatomic, weak) id delegate; 15 | 16 | @property (nonatomic, strong) NSMutableArray *allAttribures; 17 | @property (nonatomic, strong) NSMutableArray *sectionAttribures; 18 | @property (nonatomic, strong) NSMutableArray *headerOrFooterAttribures; 19 | @property (nonatomic, assign) CGSize contentSize; 20 | 21 | @end 22 | 23 | @implementation XCWaterCollectionViewLayout 24 | 25 | - (NSMutableArray *)allAttribures{ 26 | if (!_allAttribures) { 27 | _allAttribures = [NSMutableArray arrayWithCapacity:0]; 28 | } 29 | return _allAttribures; 30 | } 31 | 32 | - (NSMutableArray *)sectionAttribures{ 33 | if (!_sectionAttribures) { 34 | _sectionAttribures = [NSMutableArray arrayWithCapacity:0]; 35 | } 36 | return _sectionAttribures; 37 | } 38 | 39 | - (NSMutableArray *)headerOrFooterAttribures{ 40 | if (!_headerOrFooterAttribures) { 41 | _headerOrFooterAttribures = [NSMutableArray arrayWithCapacity:0]; 42 | } 43 | return _headerOrFooterAttribures; 44 | } 45 | 46 | - (instancetype)init{ 47 | if (self = [super init]) { 48 | self.scrollDirection = XCWaterCollectionViewScrollDirectionVertical; 49 | self.itemColumns = 2; 50 | self.itemSpace = 0.f; 51 | self.lineSpace = 0.f; 52 | self.headerViewSize = CGSizeZero; 53 | self.footerSize = CGSizeZero; 54 | self.sectioinInsets = UIEdgeInsetsZero; 55 | self.itemWidthOrHeight = 0.f; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)prepareLayout{ 61 | [super prepareLayout]; 62 | if (self.scrollDirection == XCWaterCollectionViewScrollDirectionHorizontal) { 63 | [self generateHorizontalLayouts]; 64 | }else{ 65 | [self generateVerticalLayouts]; 66 | } 67 | } 68 | 69 | - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{ 70 | return self.allAttribures; 71 | } 72 | 73 | - (CGSize)collectionViewContentSize{ 74 | return _contentSize; 75 | } 76 | 77 | - (void)generateVerticalLayouts{ 78 | 79 | self.delegate = self.collectionView.delegate; 80 | 81 | NSInteger columns = self.itemColumns; 82 | if ([self.delegate respondsToSelector:@selector(itemColumns)]) { 83 | columns = [self.delegate itemColumns]; 84 | } 85 | if (columns <= 0) { 86 | columns = 1; 87 | } 88 | 89 | 90 | NSInteger sections = self.collectionView.numberOfSections; 91 | 92 | 93 | [self.allAttribures removeAllObjects]; 94 | [self.sectionAttribures removeAllObjects]; 95 | [self.headerOrFooterAttribures removeAllObjects]; 96 | 97 | NSMutableArray *columnsHeight = [NSMutableArray arrayWithCapacity:columns]; 98 | for (int i = 0; i < columns; i++) { 99 | [columnsHeight addObject:[NSNumber numberWithFloat:0.f]]; 100 | } 101 | 102 | for (NSInteger section = 0; section < sections; section++) { 103 | 104 | UIEdgeInsets sectionInsets = self.sectioinInsets; 105 | CGFloat itemSpace = self.itemSpace; 106 | CGFloat lineSpace = self.lineSpace; 107 | 108 | if ([self.delegate respondsToSelector:@selector(sectionInsetsAtSection:)]) { 109 | sectionInsets = [self.delegate sectionInsetsAtSection:section]; 110 | } 111 | if ([self.delegate respondsToSelector:@selector(itemSpaceAtSectioin:)]) { 112 | itemSpace = [self.delegate itemSpaceAtSectioin:section]; 113 | } 114 | if ([self.delegate respondsToSelector:@selector(itemLineSpaceAtSection:)]) { 115 | lineSpace = [self.delegate itemLineSpaceAtSection:section]; 116 | } 117 | 118 | 119 | 120 | UICollectionViewLayoutAttributes *headerAttribure = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; 121 | CGSize headerSize = self.headerViewSize; 122 | if ([self.delegate respondsToSelector:@selector(headerViewSizeAtSection:)]) { 123 | headerSize = [self.delegate headerViewSizeAtSection:section]; 124 | } 125 | 126 | CGFloat maxHeight = [self getMaxHeight:columnsHeight]; 127 | headerAttribure.frame = CGRectMake(0, maxHeight, headerSize.width, headerSize.height); 128 | [self.headerOrFooterAttribures addObject:headerAttribure]; 129 | [self.allAttribures addObject:headerAttribure]; 130 | for (NSInteger i = 0;i < columnsHeight.count; i++) { 131 | [columnsHeight replaceObjectAtIndex:i withObject:[NSNumber numberWithFloat:CGRectGetMaxY(headerAttribure.frame) + sectionInsets.top]]; 132 | } 133 | 134 | //单个区 135 | CGFloat itemWidth = floor((CGRectGetWidth(self.collectionView.frame) - sectionInsets.left - sectionInsets.right - itemSpace * (columns - 1)) / columns); 136 | NSInteger items = [self.collectionView numberOfItemsInSection:section]; 137 | NSMutableArray *sectionAtributes = [NSMutableArray arrayWithCapacity:0]; 138 | for (NSInteger item = 0; item < items; item++) { 139 | CGFloat itemHeight = self.itemWidthOrHeight; 140 | if ([self.delegate respondsToSelector:@selector(itemHeightAtIndexPath:)]) { 141 | itemHeight = [self.delegate itemHeightAtIndexPath:[NSIndexPath indexPathForItem:item inSection:section]]; 142 | } 143 | 144 | UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:item inSection:section]]; 145 | NSInteger minHeightIndex = [self getMineHeightIndex:columnsHeight]; 146 | 147 | CGFloat itemX = 0; 148 | CGFloat itemY = 0; 149 | if (sectionAtributes.count < columns) { 150 | itemY = [columnsHeight[minHeightIndex] floatValue]; 151 | }else{ 152 | itemY = [columnsHeight[minHeightIndex] floatValue] + lineSpace; 153 | } 154 | itemX = sectionInsets.left + minHeightIndex * (itemWidth + itemSpace); 155 | attribute.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight); 156 | [self.allAttribures addObject:attribute]; 157 | [sectionAtributes addObject:attribute]; 158 | [columnsHeight replaceObjectAtIndex:minHeightIndex withObject:[NSNumber numberWithFloat:CGRectGetMaxY(attribute.frame)]]; 159 | } 160 | 161 | UICollectionViewLayoutAttributes *footerAttribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; 162 | CGSize footerSize = self.footerSize; 163 | if ([self.delegate respondsToSelector:@selector(footerViewSizeAtSection:)]) { 164 | footerSize = [self.delegate footerViewSizeAtSection:section]; 165 | } 166 | maxHeight = [self getMaxHeight:columnsHeight]; 167 | footerAttribute.frame = CGRectMake(0, maxHeight + sectionInsets.bottom, footerSize.width, footerSize.height); 168 | [self.headerOrFooterAttribures addObject:footerAttribute]; 169 | [self.sectionAttribures addObject:sectionAtributes]; 170 | [self.allAttribures addObject:footerAttribute]; 171 | for (NSInteger i = 0;i < columnsHeight.count; i++) { 172 | [columnsHeight replaceObjectAtIndex:i withObject:[NSNumber numberWithFloat:CGRectGetMaxY(footerAttribute.frame)]]; 173 | } 174 | } 175 | _contentSize = CGSizeMake(CGRectGetWidth(self.collectionView.frame), [self getMaxHeight:columnsHeight]); 176 | } 177 | 178 | - (void)generateHorizontalLayouts{ 179 | 180 | self.delegate = self.collectionView.delegate; 181 | 182 | NSInteger columns = self.itemColumns; 183 | if ([self.delegate respondsToSelector:@selector(itemColumns)]) { 184 | columns = [self.delegate itemColumns]; 185 | } 186 | if (columns <= 0) { 187 | columns = 1; 188 | } 189 | 190 | NSInteger sections = self.collectionView.numberOfSections; 191 | 192 | [self.allAttribures removeAllObjects]; 193 | [self.sectionAttribures removeAllObjects]; 194 | [self.headerOrFooterAttribures removeAllObjects]; 195 | 196 | NSMutableArray *columnsHeight = [NSMutableArray arrayWithCapacity:columns]; 197 | for (int i = 0; i < columns; i++) { 198 | [columnsHeight addObject:[NSNumber numberWithFloat:0.f]]; 199 | } 200 | 201 | for (NSInteger section = 0; section < sections; section++) { 202 | 203 | UIEdgeInsets sectionInsets = self.sectioinInsets; 204 | CGFloat itemSpace = self.itemSpace; 205 | CGFloat lineSpace = self.lineSpace; 206 | 207 | if ([self.delegate respondsToSelector:@selector(sectionInsetsAtSection:)]) { 208 | sectionInsets = [self.delegate sectionInsetsAtSection:section]; 209 | } 210 | if ([self.delegate respondsToSelector:@selector(itemSpaceAtSectioin:)]) { 211 | itemSpace = [self.delegate itemSpaceAtSectioin:section]; 212 | } 213 | if ([self.delegate respondsToSelector:@selector(itemLineSpaceAtSection:)]) { 214 | lineSpace = [self.delegate itemLineSpaceAtSection:section]; 215 | } 216 | 217 | UICollectionViewLayoutAttributes *headerAttribure = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; 218 | CGSize headerSize = self.headerViewSize; 219 | if ([self.delegate respondsToSelector:@selector(headerViewSizeAtSection:)]) { 220 | headerSize = [self.delegate headerViewSizeAtSection:section]; 221 | } 222 | 223 | CGFloat maxHeight = [self getMaxHeight:columnsHeight]; 224 | headerAttribure.frame = CGRectMake(maxHeight, 0, headerSize.width, headerSize.height); 225 | [self.headerOrFooterAttribures addObject:headerAttribure]; 226 | [self.allAttribures addObject:headerAttribure]; 227 | for (NSInteger i = 0;i < columnsHeight.count; i++) { 228 | [columnsHeight replaceObjectAtIndex:i withObject:[NSNumber numberWithFloat:CGRectGetMaxX(headerAttribure.frame) + sectionInsets.left]]; 229 | } 230 | 231 | //单个区 232 | CGFloat itemHeight = floor((CGRectGetHeight(self.collectionView.frame) - sectionInsets.top - sectionInsets.bottom - lineSpace * (columns - 1)) / columns); 233 | NSInteger items = [self.collectionView numberOfItemsInSection:section]; 234 | NSMutableArray *sectionAtributes = [NSMutableArray arrayWithCapacity:0]; 235 | for (NSInteger item = 0; item < items; item++) { 236 | CGFloat itemWidth = self.itemWidthOrHeight; 237 | if ([self.delegate respondsToSelector:@selector(itemHeightAtIndexPath:)]) { 238 | itemWidth = [self.delegate itemHeightAtIndexPath:[NSIndexPath indexPathForItem:item inSection:section]]; 239 | } 240 | 241 | UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:item inSection:section]]; 242 | NSInteger minHeightIndex = [self getMineHeightIndex:columnsHeight]; 243 | 244 | CGFloat itemX = 0; 245 | CGFloat itemY = 0; 246 | if (sectionAtributes.count < columns) { 247 | itemX = [columnsHeight[minHeightIndex] floatValue]; 248 | }else{ 249 | itemX = [columnsHeight[minHeightIndex] floatValue] + itemSpace; 250 | } 251 | itemY = sectionInsets.top + minHeightIndex * (itemHeight + lineSpace); 252 | attribute.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight); 253 | [self.allAttribures addObject:attribute]; 254 | [sectionAtributes addObject:attribute]; 255 | [columnsHeight replaceObjectAtIndex:minHeightIndex withObject:[NSNumber numberWithFloat:CGRectGetMaxX(attribute.frame)]]; 256 | } 257 | 258 | UICollectionViewLayoutAttributes *footerAttribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; 259 | CGSize footerSize = self.footerSize; 260 | if ([self.delegate respondsToSelector:@selector(footerViewSizeAtSection:)]) { 261 | footerSize = [self.delegate footerViewSizeAtSection:section]; 262 | } 263 | maxHeight = [self getMaxHeight:columnsHeight]; 264 | footerAttribute.frame = CGRectMake(maxHeight + sectionInsets.right, 0, footerSize.width, footerSize.height); 265 | [self.headerOrFooterAttribures addObject:footerAttribute]; 266 | [self.sectionAttribures addObject:sectionAtributes]; 267 | [self.allAttribures addObject:footerAttribute]; 268 | for (NSInteger i = 0;i < columnsHeight.count; i++) { 269 | [columnsHeight replaceObjectAtIndex:i withObject:[NSNumber numberWithFloat:CGRectGetMaxX(footerAttribute.frame)]]; 270 | } 271 | } 272 | _contentSize = CGSizeMake([self getMaxHeight:columnsHeight],CGRectGetHeight(self.collectionView.frame)); 273 | } 274 | 275 | 276 | 277 | - (NSInteger)getMineHeightIndex:(NSArray *)heights{ 278 | if (heights.count > 0) { 279 | CGFloat minHeight = [heights[0] floatValue]; 280 | NSInteger index = 0; 281 | for (NSNumber *height in heights) { 282 | if ([height floatValue] < minHeight) { 283 | minHeight = [height floatValue]; 284 | index = [heights indexOfObject:height]; 285 | } 286 | } 287 | return index; 288 | } 289 | return 0; 290 | } 291 | 292 | - (CGFloat)getMaxHeight:(NSArray *)heights{ 293 | CGFloat maxHeight = [[heights firstObject] floatValue]; 294 | for (NSNumber *height in heights) { 295 | maxHeight = MAX(maxHeight, [height floatValue]); 296 | } 297 | return maxHeight; 298 | } 299 | 300 | - (CGFloat)getMinHeight:(NSArray *)heights{ 301 | CGFloat minHeight = [[heights firstObject] floatValue]; 302 | for (NSNumber *height in heights) { 303 | minHeight = MIN(minHeight, [height floatValue]); 304 | } 305 | return minHeight; 306 | } 307 | 308 | @end 309 | 310 | -------------------------------------------------------------------------------- /XCollectionViewLayout/XCollectionViewLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCollectionViewLayout.h 3 | // PageTest 4 | // 5 | // Created by 王文科 on 2019/6/17. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | 14 | typedef NS_ENUM(NSInteger,XCollectionViewLayoutTransformStyle){ 15 | XCollectionViewLayout_LinerTransform,//线性放大 16 | XCollectionViewLayout_AffineTranform,//旋转放大 17 | }; 18 | 19 | //滚动放大模式(只支持横向滚动,不支持多分区) 20 | @interface XCollectionViewLayout : UICollectionViewFlowLayout 21 | 22 | @property (nonatomic) XCollectionViewLayoutTransformStyle transformStyle; 23 | @property (nonatomic) CGFloat scaleCoefficient;//缩小系数,default 0.8,建议0.6~0.8 24 | 25 | 26 | 27 | @end 28 | 29 | 30 | 31 | @interface UICollectionViewLayout(AutoScrollToCenterClickCell) 32 | 33 | /** 点击cell时判断cell是否在屏幕中央 34 | * 当前cell在屏幕正中央时返回 YES 35 | * 当前cell不在屏幕正中央时自动滚动到点击的cell 36 | */ 37 | - (BOOL)clickCellAtIndexPath:(NSIndexPath *)indexPath; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /XCollectionViewLayout/XCollectionViewLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCollectionViewLayout.m 3 | // PageTest 4 | // 5 | // Created by 王文科 on 2019/6/17. 6 | // Copyright © 2019 xiaocan. All rights reserved. 7 | // 8 | 9 | #import "XCollectionViewLayout.h" 10 | #import 11 | 12 | 13 | @implementation XCollectionViewLayout 14 | 15 | 16 | - (instancetype)init{ 17 | self = [super init]; 18 | if (self) { 19 | self.scaleCoefficient = 0.8; 20 | self.transformStyle = XCollectionViewLayout_LinerTransform; 21 | self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 22 | } 23 | return self; 24 | } 25 | 26 | 27 | - (void)prepareLayout{ 28 | [super prepareLayout]; 29 | UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; 30 | if (attribute) { 31 | CGFloat offset = CGRectGetWidth(self.collectionView.frame) / 2.0 - CGRectGetWidth(attribute.frame) / 2.0; 32 | self.collectionView.contentInset = UIEdgeInsetsMake(0, offset, 0, offset); 33 | } 34 | } 35 | 36 | 37 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ 38 | 39 | CGFloat lineSpacing = [self getXCLineSpacing]; 40 | NSArray *attributes = [super layoutAttributesForElementsInRect:rect]; 41 | CGRect visibleFrame = CGRectMake(self.collectionView.contentOffset.x, 0, CGRectGetWidth(self.collectionView.frame), CGRectGetHeight(self.collectionView.frame)); 42 | UICollectionViewLayoutAttributes *centerAttribute = nil; 43 | CGFloat distance = CGFLOAT_MAX; 44 | for (UICollectionViewLayoutAttributes *attribute in attributes) { 45 | CGFloat centerDistance = CGRectGetMidX(attribute.frame) - CGRectGetMidX(visibleFrame); 46 | if (ABS(centerDistance) < distance) { 47 | distance = ABS(centerDistance); 48 | centerAttribute = attribute; 49 | } 50 | CGFloat coefficient = 0.f; 51 | CGFloat baseSpace = ABS(CGRectGetWidth(centerAttribute.frame) + lineSpacing); 52 | if (ABS(centerDistance) / baseSpace >= 1.0) { 53 | coefficient = 0; 54 | }else{ 55 | coefficient = 1.0 - ABS(centerDistance) / baseSpace; 56 | } 57 | if (self.transformStyle == XCollectionViewLayout_AffineTranform) { 58 | CGFloat roration = 0.f; 59 | if (centerDistance / baseSpace >= 1.0) { 60 | roration = -M_PI_4; 61 | }else if (centerDistance / baseSpace <= -1.0) { 62 | roration = M_PI_4; 63 | }else if (centerDistance == 0) { 64 | roration = 0.f; 65 | }else if (centerDistance < 0){//left 66 | roration = (1.0 - coefficient) * M_PI_4; 67 | }else if (centerDistance > 0){//right 68 | roration = -(1.0 - coefficient) * M_PI_4; 69 | } 70 | CATransform3D transform = CATransform3DIdentity; 71 | transform.m34 = -1.0 / 500.0; 72 | 73 | transform = CATransform3DScale(transform, self.scaleCoefficient + (1 - self.scaleCoefficient) * coefficient, self.scaleCoefficient + (1 - self.scaleCoefficient) * coefficient, 1.0); 74 | attribute.transform3D = CATransform3DRotate(transform, roration, 0, 1.0, 0);//CATransform3DMakeRotation(M_PI_4, roration, 0, 0); 75 | }else{ 76 | attribute.transform = CGAffineTransformMakeScale(self.scaleCoefficient + (1 - self.scaleCoefficient) * coefficient, self.scaleCoefficient + (1 - self.scaleCoefficient) * coefficient); 77 | } 78 | } 79 | return attributes; 80 | } 81 | 82 | - (CGFloat)getXCLineSpacing{ 83 | CGFloat lineSpacing = self.minimumLineSpacing; 84 | if ([self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]) { 85 | lineSpacing = [(id)self.collectionView.delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0]; 86 | } 87 | return lineSpacing; 88 | } 89 | 90 | - (CGFloat)getXCItemSpacing{ 91 | CGFloat itemSpacing = self.minimumInteritemSpacing; 92 | if ([self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) { 93 | itemSpacing = [(id)self.collectionView.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0]; 94 | } 95 | return itemSpacing; 96 | } 97 | 98 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ 99 | return YES; 100 | } 101 | 102 | - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ 103 | CGRect visibleFrame = CGRectMake(proposedContentOffset.x, 0, CGRectGetWidth(self.collectionView.frame), CGRectGetHeight(self.collectionView.frame)); 104 | UICollectionViewLayoutAttributes *centerAttribute = nil; 105 | CGFloat distance = CGFLOAT_MAX; 106 | NSInteger sections = [self.collectionView numberOfSections]; 107 | for (NSInteger section = 0; section < sections; section++) { 108 | NSInteger items = [self.collectionView numberOfItemsInSection:section]; 109 | for (NSInteger item = 0; item < items; item++) { 110 | UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:section]]; 111 | if (attribute) { 112 | CGFloat centerDistance = ABS(attribute.frame.origin.x + attribute.frame.size.width / 2 - visibleFrame.origin.x - visibleFrame.size.width / 2); 113 | if (centerDistance < distance) { 114 | distance = centerDistance; 115 | centerAttribute = attribute; 116 | proposedContentOffset = CGPointMake(CGRectGetMinX(attribute.frame) - CGRectGetWidth(self.collectionView.frame) / 2.0 + CGRectGetWidth(attribute.frame) / 2.0 , 0); 117 | } 118 | } 119 | } 120 | } 121 | return proposedContentOffset; 122 | } 123 | 124 | - (void)setScrollDirection:(UICollectionViewScrollDirection)scrollDirection{ 125 | [super setScrollDirection:UICollectionViewScrollDirectionHorizontal]; 126 | } 127 | 128 | - (void)setTransformStyle:(XCollectionViewLayoutTransformStyle)transformStyle{ 129 | _transformStyle = transformStyle; 130 | if (_transformStyle == XCollectionViewLayout_AffineTranform) { 131 | self.minimumLineSpacing = -70.0; 132 | }else{ 133 | self.minimumLineSpacing = 0; 134 | } 135 | } 136 | 137 | @end 138 | 139 | 140 | 141 | @implementation UICollectionViewLayout(AutoScrollToCenterClickCell) 142 | 143 | 144 | + (void)load{ 145 | Class class = [self superclass]; 146 | SEL select = @selector(clickCellAtIndexPath:); 147 | IMP implementation = class_getMethodImplementation(class, select); 148 | class_addMethod(class, select, implementation, method_getTypeEncoding(class_getInstanceMethod(class, select))); 149 | } 150 | 151 | 152 | /** 点击cell时判断cell是否在屏幕中央 153 | * 当前cell在屏幕正中央时返回 YES 154 | * 当前cell不在屏幕正中央时自动滚动到点击的cell 155 | */ 156 | - (BOOL)clickCellAtIndexPath:(NSIndexPath *)indexPath{ 157 | if (indexPath) { 158 | UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath]; 159 | if (attribute) { 160 | CGFloat centerX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.frame) / 2.0; 161 | if (ABS(CGRectGetMidX(attribute.frame) - centerX) <= 5.0) { 162 | return YES; 163 | } 164 | CGPoint offset = self.collectionView.contentOffset; 165 | offset.x = CGRectGetMidX(attribute.frame) - centerX + offset.x; 166 | offset = [self targetContentOffsetForProposedContentOffset:offset withScrollingVelocity:CGPointZero]; 167 | [self.collectionView setContentOffset:offset animated:YES]; 168 | } 169 | return NO; 170 | } 171 | return NO; 172 | } 173 | 174 | @end 175 | --------------------------------------------------------------------------------