├── .gitignore ├── LICENSE ├── README.md ├── Swift-Senpai-UICollectionView-SwiftUI.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── Swift-Senpai-UICollectionView-SwiftUI ├── AppDelegate.swift ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-1024.png │ ├── Icon-120.png │ ├── Icon-121.png │ ├── Icon-180.png │ ├── Icon-40.png │ ├── Icon-58.png │ ├── Icon-60.png │ ├── Icon-80.png │ └── Icon-87.png └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Custom Header & Footer ├── FooterView.swift ├── HeaderFooterViewController.swift └── HeaderView.swift ├── Handle User Interactions ├── UserInteractionCell.swift └── UserInteractionViewController.swift ├── Info.plist ├── Model.swift ├── Placeholder cell ├── DataProvider.swift ├── PlaceholderCellViewController.swift ├── PlaceholderModifier.swift └── QuoteCell.swift ├── Refresh Cells Content ├── TodoListCell.swift └── TodoListViewController.swift ├── SceneDelegate.swift ├── SwiftUI Custom Cell ├── MyFirstSwiftUICell.swift └── SwiftUICustomCellViewController.swift └── UIKit Subview ├── AgreementView.swift ├── KSSwitchButton.swift ├── Model ├── ChartStrideBy.swift ├── ChartType.swift └── InterpolationMethod.swift └── UIKitSubviewViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Lee Kah Seng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftSenpai-UICollectionView-SwiftUI 2 | - Sample project for article '[How To Create Custom UICollectionViewListCell Using SwiftUI](https://swiftsenpai.com/development/swiftui-custom-cell/)'. 3 | - Sample project for article '[Handling Cell Interactions When Using UIHostingConfiguration in iOS 16](https://swiftsenpai.com/development/uihostingconfiguration-cell-interactions/)'. 4 | - Sample project for article '[How to Refresh Cell’s Content When Using UIHostingConfiguration](https://swiftsenpai.com/development/refresh-cells-uihostingconfiguration/)'. 5 | - Sample project for article '[How to Create Custom Header & Footer Using UIHostingConfiguration](https://swiftsenpai.com/development/uihostingconfiguration-custom-header/)'. 6 | - Sample project for article '[How to Use UIHostingConfiguration to Integrate SwiftUI Views into UIKit Apps](https://swiftsenpai.com/development/uihostingconfiguration-subview/)'. 7 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 18074DFF28A7E27E00FDA40A /* UserInteractionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18074DFE28A7E27E00FDA40A /* UserInteractionViewController.swift */; }; 11 | 1815100729E167FE00392345 /* UIKitSubviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1815100629E167FE00392345 /* UIKitSubviewViewController.swift */; }; 12 | 1815100929E168BF00392345 /* KSSwitchButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1815100829E168BF00392345 /* KSSwitchButton.swift */; }; 13 | 18330EAF289E22D200549E96 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18330EAE289E22D200549E96 /* AppDelegate.swift */; }; 14 | 18330EB1289E22D200549E96 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18330EB0289E22D200549E96 /* SceneDelegate.swift */; }; 15 | 18330EB6289E22D200549E96 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 18330EB4289E22D200549E96 /* Main.storyboard */; }; 16 | 18330EB8289E22D700549E96 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18330EB7289E22D700549E96 /* Assets.xcassets */; }; 17 | 18330EBB289E22D700549E96 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 18330EB9289E22D700549E96 /* LaunchScreen.storyboard */; }; 18 | 18330EC5289E251300549E96 /* SwiftUICustomCellViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18330EC4289E251300549E96 /* SwiftUICustomCellViewController.swift */; }; 19 | 18330EC7289E26FB00549E96 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18330EC6289E26FB00549E96 /* Model.swift */; }; 20 | 18330EC9289E3A9400549E96 /* MyFirstSwiftUICell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18330EC8289E3A9400549E96 /* MyFirstSwiftUICell.swift */; }; 21 | 1874FD4729E2CE1D00747876 /* AgreementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1874FD4629E2CE1D00747876 /* AgreementView.swift */; }; 22 | 187F7A8028AA7FCD00A053CF /* UserInteractionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187F7A7F28AA7FCC00A053CF /* UserInteractionCell.swift */; }; 23 | 1890316728DB3BF500004F0F /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1890316628DB3BF400004F0F /* HeaderView.swift */; }; 24 | 1890316928DB47DA00004F0F /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1890316828DB47DA00004F0F /* FooterView.swift */; }; 25 | 18A4A99428F4546B008B1D20 /* PlaceholderCellViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A4A99328F4546B008B1D20 /* PlaceholderCellViewController.swift */; }; 26 | 18ABA49C28C6210400177E59 /* TodoListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18ABA49B28C6210400177E59 /* TodoListViewController.swift */; }; 27 | 18ABA49E28C6211C00177E59 /* TodoListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18ABA49D28C6211C00177E59 /* TodoListCell.swift */; }; 28 | 18C059AC28FA932E00903BA9 /* QuoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C059AB28FA932E00903BA9 /* QuoteCell.swift */; }; 29 | 18C059AE28FA95A200903BA9 /* PlaceholderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C059AD28FA95A200903BA9 /* PlaceholderModifier.swift */; }; 30 | 18C059B028FA96D200903BA9 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C059AF28FA96D200903BA9 /* DataProvider.swift */; }; 31 | 18E05B4B28D9FB0800F0C78F /* HeaderFooterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E05B4A28D9FB0800F0C78F /* HeaderFooterViewController.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 18074DFE28A7E27E00FDA40A /* UserInteractionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInteractionViewController.swift; sourceTree = ""; }; 36 | 1815100629E167FE00392345 /* UIKitSubviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitSubviewViewController.swift; sourceTree = ""; }; 37 | 1815100829E168BF00392345 /* KSSwitchButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSSwitchButton.swift; sourceTree = ""; }; 38 | 18330EAB289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swift-Senpai-UICollectionView-SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 18330EAE289E22D200549E96 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 18330EB0289E22D200549E96 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 41 | 18330EB5289E22D200549E96 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 18330EB7289E22D700549E96 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 18330EBA289E22D700549E96 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 18330EBC289E22D700549E96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 18330EC4289E251300549E96 /* SwiftUICustomCellViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICustomCellViewController.swift; sourceTree = ""; }; 46 | 18330EC6289E26FB00549E96 /* Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 47 | 18330EC8289E3A9400549E96 /* MyFirstSwiftUICell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyFirstSwiftUICell.swift; sourceTree = ""; }; 48 | 1874FD4629E2CE1D00747876 /* AgreementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementView.swift; sourceTree = ""; }; 49 | 187F7A7F28AA7FCC00A053CF /* UserInteractionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInteractionCell.swift; sourceTree = ""; }; 50 | 1890316628DB3BF400004F0F /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; 51 | 1890316828DB47DA00004F0F /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; 52 | 18A4A99328F4546B008B1D20 /* PlaceholderCellViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderCellViewController.swift; sourceTree = ""; }; 53 | 18ABA49B28C6210400177E59 /* TodoListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListViewController.swift; sourceTree = ""; }; 54 | 18ABA49D28C6211C00177E59 /* TodoListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListCell.swift; sourceTree = ""; }; 55 | 18C059AB28FA932E00903BA9 /* QuoteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteCell.swift; sourceTree = ""; }; 56 | 18C059AD28FA95A200903BA9 /* PlaceholderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderModifier.swift; sourceTree = ""; }; 57 | 18C059AF28FA96D200903BA9 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; 58 | 18E05B4A28D9FB0800F0C78F /* HeaderFooterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFooterViewController.swift; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 18330EA8289E22D200549E96 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 18074DFD28A7E25200FDA40A /* Handle User Interactions */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 18074DFE28A7E27E00FDA40A /* UserInteractionViewController.swift */, 76 | 187F7A7F28AA7FCC00A053CF /* UserInteractionCell.swift */, 77 | ); 78 | path = "Handle User Interactions"; 79 | sourceTree = ""; 80 | }; 81 | 1815100529E167D500392345 /* UIKit Subview */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 1815100629E167FE00392345 /* UIKitSubviewViewController.swift */, 85 | 1874FD4629E2CE1D00747876 /* AgreementView.swift */, 86 | 1815100829E168BF00392345 /* KSSwitchButton.swift */, 87 | ); 88 | path = "UIKit Subview"; 89 | sourceTree = ""; 90 | }; 91 | 1815100A29E198B600392345 /* SwiftUI */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | ); 95 | path = SwiftUI; 96 | sourceTree = ""; 97 | }; 98 | 18330EA2289E22D200549E96 = { 99 | isa = PBXGroup; 100 | children = ( 101 | 18330EAD289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI */, 102 | 18330EAC289E22D200549E96 /* Products */, 103 | ); 104 | sourceTree = ""; 105 | }; 106 | 18330EAC289E22D200549E96 /* Products */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 18330EAB289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI.app */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | 18330EAD289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 18330EC3289E233A00549E96 /* SwiftUI Custom Cell */, 118 | 18074DFD28A7E25200FDA40A /* Handle User Interactions */, 119 | 18ABA49A28C6204400177E59 /* Refresh Cells Content */, 120 | 1893109028D9F42C008BD5F4 /* Custom Header & Footer */, 121 | 18A4A99228F453D1008B1D20 /* Placeholder cell */, 122 | 1815100529E167D500392345 /* UIKit Subview */, 123 | 18330EAE289E22D200549E96 /* AppDelegate.swift */, 124 | 18330EB0289E22D200549E96 /* SceneDelegate.swift */, 125 | 18330EC6289E26FB00549E96 /* Model.swift */, 126 | 18330EB4289E22D200549E96 /* Main.storyboard */, 127 | 18330EB7289E22D700549E96 /* Assets.xcassets */, 128 | 18330EB9289E22D700549E96 /* LaunchScreen.storyboard */, 129 | 18330EBC289E22D700549E96 /* Info.plist */, 130 | ); 131 | path = "Swift-Senpai-UICollectionView-SwiftUI"; 132 | sourceTree = ""; 133 | }; 134 | 18330EC3289E233A00549E96 /* SwiftUI Custom Cell */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 1815100A29E198B600392345 /* SwiftUI */, 138 | 18330EC4289E251300549E96 /* SwiftUICustomCellViewController.swift */, 139 | 18330EC8289E3A9400549E96 /* MyFirstSwiftUICell.swift */, 140 | ); 141 | path = "SwiftUI Custom Cell"; 142 | sourceTree = ""; 143 | }; 144 | 1893109028D9F42C008BD5F4 /* Custom Header & Footer */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 18E05B4A28D9FB0800F0C78F /* HeaderFooterViewController.swift */, 148 | 1890316628DB3BF400004F0F /* HeaderView.swift */, 149 | 1890316828DB47DA00004F0F /* FooterView.swift */, 150 | ); 151 | path = "Custom Header & Footer"; 152 | sourceTree = ""; 153 | }; 154 | 18A4A99228F453D1008B1D20 /* Placeholder cell */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 18A4A99328F4546B008B1D20 /* PlaceholderCellViewController.swift */, 158 | 18C059AB28FA932E00903BA9 /* QuoteCell.swift */, 159 | 18C059AD28FA95A200903BA9 /* PlaceholderModifier.swift */, 160 | 18C059AF28FA96D200903BA9 /* DataProvider.swift */, 161 | ); 162 | path = "Placeholder cell"; 163 | sourceTree = ""; 164 | }; 165 | 18ABA49A28C6204400177E59 /* Refresh Cells Content */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 18ABA49B28C6210400177E59 /* TodoListViewController.swift */, 169 | 18ABA49D28C6211C00177E59 /* TodoListCell.swift */, 170 | ); 171 | path = "Refresh Cells Content"; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | 18330EAA289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 18330EBF289E22D700549E96 /* Build configuration list for PBXNativeTarget "Swift-Senpai-UICollectionView-SwiftUI" */; 180 | buildPhases = ( 181 | 18330EA7289E22D200549E96 /* Sources */, 182 | 18330EA8289E22D200549E96 /* Frameworks */, 183 | 18330EA9289E22D200549E96 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = "Swift-Senpai-UICollectionView-SwiftUI"; 190 | productName = "Swift-Senpai-UICollectionView-SwiftUI"; 191 | productReference = 18330EAB289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI.app */; 192 | productType = "com.apple.product-type.application"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | 18330EA3289E22D200549E96 /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | BuildIndependentTargetsInParallel = 1; 201 | LastSwiftUpdateCheck = 1400; 202 | LastUpgradeCheck = 1400; 203 | TargetAttributes = { 204 | 18330EAA289E22D200549E96 = { 205 | CreatedOnToolsVersion = 14.0; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 18330EA6289E22D200549E96 /* Build configuration list for PBXProject "Swift-Senpai-UICollectionView-SwiftUI" */; 210 | compatibilityVersion = "Xcode 14.0"; 211 | developmentRegion = en; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | Base, 216 | ); 217 | mainGroup = 18330EA2289E22D200549E96; 218 | productRefGroup = 18330EAC289E22D200549E96 /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | 18330EAA289E22D200549E96 /* Swift-Senpai-UICollectionView-SwiftUI */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | 18330EA9289E22D200549E96 /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 18330EBB289E22D700549E96 /* LaunchScreen.storyboard in Resources */, 233 | 18330EB8289E22D700549E96 /* Assets.xcassets in Resources */, 234 | 18330EB6289E22D200549E96 /* Main.storyboard in Resources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXResourcesBuildPhase section */ 239 | 240 | /* Begin PBXSourcesBuildPhase section */ 241 | 18330EA7289E22D200549E96 /* Sources */ = { 242 | isa = PBXSourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 18ABA49C28C6210400177E59 /* TodoListViewController.swift in Sources */, 246 | 18A4A99428F4546B008B1D20 /* PlaceholderCellViewController.swift in Sources */, 247 | 18C059B028FA96D200903BA9 /* DataProvider.swift in Sources */, 248 | 1815100929E168BF00392345 /* KSSwitchButton.swift in Sources */, 249 | 18074DFF28A7E27E00FDA40A /* UserInteractionViewController.swift in Sources */, 250 | 18E05B4B28D9FB0800F0C78F /* HeaderFooterViewController.swift in Sources */, 251 | 18C059AC28FA932E00903BA9 /* QuoteCell.swift in Sources */, 252 | 18330EC5289E251300549E96 /* SwiftUICustomCellViewController.swift in Sources */, 253 | 18330EAF289E22D200549E96 /* AppDelegate.swift in Sources */, 254 | 18C059AE28FA95A200903BA9 /* PlaceholderModifier.swift in Sources */, 255 | 18ABA49E28C6211C00177E59 /* TodoListCell.swift in Sources */, 256 | 18330EB1289E22D200549E96 /* SceneDelegate.swift in Sources */, 257 | 1890316728DB3BF500004F0F /* HeaderView.swift in Sources */, 258 | 1815100729E167FE00392345 /* UIKitSubviewViewController.swift in Sources */, 259 | 18330EC9289E3A9400549E96 /* MyFirstSwiftUICell.swift in Sources */, 260 | 1890316928DB47DA00004F0F /* FooterView.swift in Sources */, 261 | 187F7A8028AA7FCD00A053CF /* UserInteractionCell.swift in Sources */, 262 | 18330EC7289E26FB00549E96 /* Model.swift in Sources */, 263 | 1874FD4729E2CE1D00747876 /* AgreementView.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXSourcesBuildPhase section */ 268 | 269 | /* Begin PBXVariantGroup section */ 270 | 18330EB4289E22D200549E96 /* Main.storyboard */ = { 271 | isa = PBXVariantGroup; 272 | children = ( 273 | 18330EB5289E22D200549E96 /* Base */, 274 | ); 275 | name = Main.storyboard; 276 | sourceTree = ""; 277 | }; 278 | 18330EB9289E22D700549E96 /* LaunchScreen.storyboard */ = { 279 | isa = PBXVariantGroup; 280 | children = ( 281 | 18330EBA289E22D700549E96 /* Base */, 282 | ); 283 | name = LaunchScreen.storyboard; 284 | sourceTree = ""; 285 | }; 286 | /* End PBXVariantGroup section */ 287 | 288 | /* Begin XCBuildConfiguration section */ 289 | 18330EBD289E22D700549E96 /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | ALWAYS_SEARCH_USER_PATHS = NO; 293 | CLANG_ANALYZER_NONNULL = YES; 294 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 296 | CLANG_ENABLE_MODULES = YES; 297 | CLANG_ENABLE_OBJC_ARC = YES; 298 | CLANG_ENABLE_OBJC_WEAK = YES; 299 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 300 | CLANG_WARN_BOOL_CONVERSION = YES; 301 | CLANG_WARN_COMMA = YES; 302 | CLANG_WARN_CONSTANT_CONVERSION = YES; 303 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 304 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 305 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 306 | CLANG_WARN_EMPTY_BODY = YES; 307 | CLANG_WARN_ENUM_CONVERSION = YES; 308 | CLANG_WARN_INFINITE_RECURSION = YES; 309 | CLANG_WARN_INT_CONVERSION = YES; 310 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 311 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 312 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 313 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 314 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 315 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 316 | CLANG_WARN_STRICT_PROTOTYPES = YES; 317 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 318 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 319 | CLANG_WARN_UNREACHABLE_CODE = YES; 320 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 321 | COPY_PHASE_STRIP = NO; 322 | DEBUG_INFORMATION_FORMAT = dwarf; 323 | ENABLE_STRICT_OBJC_MSGSEND = YES; 324 | ENABLE_TESTABILITY = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu11; 326 | GCC_DYNAMIC_NO_PIC = NO; 327 | GCC_NO_COMMON_BLOCKS = YES; 328 | GCC_OPTIMIZATION_LEVEL = 0; 329 | GCC_PREPROCESSOR_DEFINITIONS = ( 330 | "DEBUG=1", 331 | "$(inherited)", 332 | ); 333 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 334 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 335 | GCC_WARN_UNDECLARED_SELECTOR = YES; 336 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 337 | GCC_WARN_UNUSED_FUNCTION = YES; 338 | GCC_WARN_UNUSED_VARIABLE = YES; 339 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 340 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 341 | MTL_FAST_MATH = YES; 342 | ONLY_ACTIVE_ARCH = YES; 343 | SDKROOT = iphoneos; 344 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 345 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 346 | }; 347 | name = Debug; 348 | }; 349 | 18330EBE289E22D700549E96 /* Release */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_ANALYZER_NONNULL = YES; 354 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_ENABLE_OBJC_WEAK = YES; 359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_COMMA = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 379 | CLANG_WARN_UNREACHABLE_CODE = YES; 380 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 383 | ENABLE_NS_ASSERTIONS = NO; 384 | ENABLE_STRICT_OBJC_MSGSEND = YES; 385 | GCC_C_LANGUAGE_STANDARD = gnu11; 386 | GCC_NO_COMMON_BLOCKS = YES; 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 394 | MTL_ENABLE_DEBUG_INFO = NO; 395 | MTL_FAST_MATH = YES; 396 | SDKROOT = iphoneos; 397 | SWIFT_COMPILATION_MODE = wholemodule; 398 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 399 | VALIDATE_PRODUCT = YES; 400 | }; 401 | name = Release; 402 | }; 403 | 18330EC0289E22D700549E96 /* Debug */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 407 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 408 | CODE_SIGN_STYLE = Automatic; 409 | CURRENT_PROJECT_VERSION = 1; 410 | DEVELOPMENT_TEAM = QVEMCYHXHL; 411 | GENERATE_INFOPLIST_FILE = YES; 412 | INFOPLIST_FILE = "Swift-Senpai-UICollectionView-SwiftUI/Info.plist"; 413 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 414 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 415 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 416 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 417 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 418 | LD_RUNPATH_SEARCH_PATHS = ( 419 | "$(inherited)", 420 | "@executable_path/Frameworks", 421 | ); 422 | MARKETING_VERSION = 1.0; 423 | PRODUCT_BUNDLE_IDENTIFIER = "com.LeeKahSeng.Swift-Senpai-UICollectionView-SwiftUI"; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 426 | SUPPORTS_MACCATALYST = NO; 427 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 428 | SWIFT_EMIT_LOC_STRINGS = YES; 429 | SWIFT_VERSION = 5.0; 430 | TARGETED_DEVICE_FAMILY = 1; 431 | }; 432 | name = Debug; 433 | }; 434 | 18330EC1289E22D700549E96 /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 438 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 439 | CODE_SIGN_STYLE = Automatic; 440 | CURRENT_PROJECT_VERSION = 1; 441 | DEVELOPMENT_TEAM = QVEMCYHXHL; 442 | GENERATE_INFOPLIST_FILE = YES; 443 | INFOPLIST_FILE = "Swift-Senpai-UICollectionView-SwiftUI/Info.plist"; 444 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 445 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 446 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 447 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 448 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 449 | LD_RUNPATH_SEARCH_PATHS = ( 450 | "$(inherited)", 451 | "@executable_path/Frameworks", 452 | ); 453 | MARKETING_VERSION = 1.0; 454 | PRODUCT_BUNDLE_IDENTIFIER = "com.LeeKahSeng.Swift-Senpai-UICollectionView-SwiftUI"; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 457 | SUPPORTS_MACCATALYST = NO; 458 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 459 | SWIFT_EMIT_LOC_STRINGS = YES; 460 | SWIFT_VERSION = 5.0; 461 | TARGETED_DEVICE_FAMILY = 1; 462 | }; 463 | name = Release; 464 | }; 465 | /* End XCBuildConfiguration section */ 466 | 467 | /* Begin XCConfigurationList section */ 468 | 18330EA6289E22D200549E96 /* Build configuration list for PBXProject "Swift-Senpai-UICollectionView-SwiftUI" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 18330EBD289E22D700549E96 /* Debug */, 472 | 18330EBE289E22D700549E96 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | 18330EBF289E22D700549E96 /* Build configuration list for PBXNativeTarget "Swift-Senpai-UICollectionView-SwiftUI" */ = { 478 | isa = XCConfigurationList; 479 | buildConfigurations = ( 480 | 18330EC0289E22D700549E96 /* Debug */, 481 | 18330EC1289E22D700549E96 /* Release */, 482 | ); 483 | defaultConfigurationIsVisible = 0; 484 | defaultConfigurationName = Release; 485 | }; 486 | /* End XCConfigurationList section */ 487 | }; 488 | rootObject = 18330EA3289E22D200549E96 /* Project object */; 489 | } 490 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 06/08/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Icon-60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Icon-58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Icon-87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Icon-80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "Icon-120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Icon-121.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "Icon-180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "Icon-1024.png", 53 | "idiom" : "ios-marketing", 54 | "scale" : "1x", 55 | "size" : "1024x1024" 56 | } 57 | ], 58 | "info" : { 59 | "author" : "xcode", 60 | "version" : 1 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-120.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-121.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-121.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-180.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-58.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-80.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-SwiftUI/4ed5b9299be329e4e29e431ddb2198b488e2b05a/Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-87.png -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/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 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Custom Header & Footer/FooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FooterView.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 21/09/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FooterView: View { 11 | 12 | @ObservedObject var section: SymbolSection 13 | 14 | var body: some View { 15 | 16 | Text(section.selectedCellIndex == nil ? 17 | "Selected: ---" : "Selected: \(section.symbols[section.selectedCellIndex!].name)") 18 | .font(.system(.footnote, weight: .semibold)) 19 | .foregroundColor(Color(.darkGray)) 20 | .frame(maxWidth: .infinity, alignment: .leading) 21 | .padding(.bottom) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Custom Header & Footer/HeaderFooterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderFooterViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 20/09/2022. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class SymbolSection: ObservableObject { 12 | let title: String 13 | let symbols: [SFSymbolItem] 14 | @Published var selectedCellIndex: Int? 15 | 16 | init(title: String, symbols: [SFSymbolItem]) { 17 | self.title = title 18 | self.symbols = symbols 19 | } 20 | } 21 | 22 | class HeaderFooterViewController: UIViewController { 23 | 24 | var collectionView: UICollectionView! 25 | private var symbolCellRegistration: UICollectionView.CellRegistration! 26 | private var headerRegistration: UICollectionView.SupplementaryRegistration! 27 | private var footerRegistration: UICollectionView.SupplementaryRegistration! 28 | 29 | let dataModel = [ 30 | SymbolSection(title: "Weather", symbols: [ 31 | SFSymbolItem(name: "sun.max.fill"), 32 | SFSymbolItem(name: "moon.fill"), 33 | SFSymbolItem(name: "sun.haze.fill"), 34 | SFSymbolItem(name: "moon.stars"), 35 | SFSymbolItem(name: "cloud.drizzle.fill"), 36 | SFSymbolItem(name: "cloud.snow"), 37 | SFSymbolItem(name: "tornado"), 38 | SFSymbolItem(name: "wind.snow"), 39 | ]), 40 | 41 | SymbolSection(title: "Health", symbols: [ 42 | SFSymbolItem(name: "brain.head.profile"), 43 | SFSymbolItem(name: "heart.text.square.fill"), 44 | SFSymbolItem(name: "heart.circle"), 45 | SFSymbolItem(name: "cross.case"), 46 | ]), 47 | 48 | SymbolSection(title: "Connectivity", symbols: [ 49 | SFSymbolItem(name: "personalhotspot.circle"), 50 | SFSymbolItem(name: "bolt.horizontal.circle.fill"), 51 | SFSymbolItem(name: "wifi.circle.fill"), 52 | SFSymbolItem(name: "antenna.radiowaves.left.and.right"), 53 | SFSymbolItem(name: "arrow.counterclockwise.icloud"), 54 | SFSymbolItem(name: "lock.icloud.fill"), 55 | ]), 56 | 57 | ] 58 | 59 | override func viewDidLoad() { 60 | super.viewDidLoad() 61 | 62 | configureCollectionView() 63 | performCellRegistration() 64 | performHeaderRegistration() 65 | performFooterRegistration() 66 | } 67 | 68 | } 69 | 70 | private extension HeaderFooterViewController { 71 | 72 | private func configureCollectionView() { 73 | 74 | let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in 75 | let fraction: CGFloat = 1 / 4 76 | let inset: CGFloat = 2.5 77 | 78 | // Item 79 | let itemSize = NSCollectionLayoutSize( 80 | widthDimension: .fractionalWidth(fraction), 81 | heightDimension: .fractionalHeight(1) 82 | ) 83 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 84 | item.contentInsets = NSDirectionalEdgeInsets( 85 | top: inset, 86 | leading: inset, 87 | bottom: inset, 88 | trailing: inset 89 | ) 90 | 91 | // Group 92 | let groupSize = NSCollectionLayoutSize( 93 | widthDimension: .fractionalWidth(1), 94 | heightDimension: .fractionalWidth(fraction) 95 | ) 96 | let group = NSCollectionLayoutGroup.horizontal( 97 | layoutSize: groupSize, 98 | subitems: [item] 99 | ) 100 | 101 | // Section 102 | let section = NSCollectionLayoutSection(group: group) 103 | section.contentInsets = NSDirectionalEdgeInsets( 104 | top: inset, 105 | leading: inset, 106 | bottom: inset, 107 | trailing: inset 108 | ) 109 | 110 | // Supplementary Item (Header) 111 | let headerItemSize = NSCollectionLayoutSize( 112 | widthDimension: .fractionalWidth(1), 113 | heightDimension: .estimated(50) 114 | ) 115 | let headerItem = NSCollectionLayoutBoundarySupplementaryItem( 116 | layoutSize: headerItemSize, 117 | elementKind: UICollectionView.elementKindSectionHeader, 118 | alignment: .top 119 | ) 120 | 121 | // Supplementary Item (Footer) 122 | let footerItemSize = NSCollectionLayoutSize( 123 | widthDimension: .fractionalWidth(1), 124 | heightDimension: .estimated(50) 125 | ) 126 | let footerItem = NSCollectionLayoutBoundarySupplementaryItem( 127 | layoutSize: footerItemSize, 128 | elementKind: UICollectionView.elementKindSectionFooter, 129 | alignment: .bottom 130 | ) 131 | 132 | section.boundarySupplementaryItems = [headerItem, footerItem] 133 | 134 | return section 135 | }) 136 | 137 | // Create collection view with list layout 138 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: compositionalLayout) 139 | collectionView.dataSource = self 140 | collectionView.delegate = self 141 | view = collectionView 142 | } 143 | 144 | private func performCellRegistration() { 145 | 146 | symbolCellRegistration = .init { cell, indexPath, item in 147 | 148 | cell.configurationUpdateHandler = { cell, state in 149 | cell.contentConfiguration = UIHostingConfiguration { 150 | 151 | Image(systemName: item.name) 152 | .imageScale(.large) 153 | .frame(maxWidth: .infinity, maxHeight: .infinity) 154 | .background { 155 | RoundedRectangle(cornerRadius: 8.0) 156 | .fill(Color(.systemYellow)) 157 | } 158 | .overlay { 159 | // Show red border when cell is selected 160 | RoundedRectangle(cornerRadius: 8.0) 161 | .stroke(state.isSelected ? Color(.systemRed) : .clear, lineWidth: 4) 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | private func performHeaderRegistration() { 169 | 170 | headerRegistration = .init(elementKind: UICollectionView.elementKindSectionHeader) { [unowned self] 171 | (header, elementKind, indexPath) in 172 | 173 | let symbolSection = dataModel[indexPath.section] 174 | 175 | header.contentConfiguration = UIHostingConfiguration { 176 | HeaderView(section: symbolSection) 177 | } 178 | } 179 | } 180 | 181 | private func performFooterRegistration() { 182 | 183 | footerRegistration = .init(elementKind: UICollectionView.elementKindSectionFooter) { [unowned self] 184 | (footer, elementKind, indexPath) in 185 | 186 | let symbolSection = dataModel[indexPath.section] 187 | 188 | footer.contentConfiguration = UIHostingConfiguration { 189 | FooterView(section: symbolSection) 190 | } 191 | } 192 | } 193 | } 194 | 195 | extension HeaderFooterViewController: UICollectionViewDataSource { 196 | 197 | func numberOfSections(in: UICollectionView) -> Int { 198 | return dataModel.count 199 | } 200 | 201 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 202 | let symbols = dataModel[section].symbols 203 | return symbols.count 204 | } 205 | 206 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 207 | let item = dataModel[indexPath.section].symbols[indexPath.item] 208 | let cell = collectionView.dequeueConfiguredReusableCell( 209 | using: symbolCellRegistration, 210 | for: indexPath, 211 | item: item 212 | ) 213 | return cell 214 | } 215 | 216 | func collectionView(_ collectionView: UICollectionView, 217 | viewForSupplementaryElementOfKind elementKind: String, 218 | at indexPath: IndexPath) -> UICollectionReusableView { 219 | 220 | switch elementKind { 221 | case UICollectionView.elementKindSectionHeader: 222 | // Dequeue header view 223 | let header = collectionView.dequeueConfiguredReusableSupplementary( 224 | using: headerRegistration, 225 | for: indexPath 226 | ) 227 | return header 228 | 229 | case UICollectionView.elementKindSectionFooter: 230 | // Dequeue footer view 231 | let footer = collectionView.dequeueConfiguredReusableSupplementary( 232 | using: footerRegistration, 233 | for: indexPath 234 | ) 235 | return footer 236 | 237 | default: 238 | fatalError("Unexpected element kind") 239 | } 240 | } 241 | } 242 | 243 | extension HeaderFooterViewController: UICollectionViewDelegate { 244 | 245 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 246 | 247 | // Remove all selection 248 | for symbolSection in dataModel { 249 | symbolSection.selectedCellIndex = nil 250 | } 251 | 252 | // Update selected cell index of the selected section 253 | let selectedSection = dataModel[indexPath.section] 254 | selectedSection.selectedCellIndex = indexPath.item 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Custom Header & Footer/HeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderView.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 21/09/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HeaderView: View { 11 | 12 | @State private var showInfo = false 13 | 14 | var section: SymbolSection 15 | 16 | var body: some View { 17 | VStack(alignment: .trailing) { 18 | HStack { 19 | Text(section.title) 20 | .font(.title) 21 | 22 | Spacer() 23 | 24 | Button { 25 | withAnimation { 26 | showInfo.toggle() 27 | } 28 | } label: { 29 | if showInfo { 30 | Image(systemName: "info.circle.fill") 31 | .imageScale(.large) 32 | .tint(.black) 33 | } else { 34 | Image(systemName: "info.circle") 35 | .imageScale(.large) 36 | .tint(.black) 37 | } 38 | } 39 | } 40 | .padding(.leading) 41 | .padding(.trailing) 42 | .frame(height: 50.0) 43 | .background { 44 | RoundedRectangle(cornerRadius: 8.0) 45 | .fill(Color(.systemGreen)) 46 | } 47 | 48 | if showInfo { 49 | Text("This section has \(section.symbols.count) symbols") 50 | .font(.footnote) 51 | .foregroundColor(Color(.darkGray)) 52 | .transition(.scale) 53 | } 54 | } 55 | } 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Handle User Interactions/UserInteractionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInteractionCell.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 15/08/2022. 6 | // 7 | 8 | import SwiftUI 9 | import UIKit 10 | 11 | struct UserInteractionCell: View { 12 | 13 | var item: SFSymbolItem 14 | 15 | /// Variable that represents the view state 16 | var state: UICellConfigurationState 17 | 18 | var body: some View { 19 | 20 | HStack(alignment: .center, spacing: 8) { 21 | 22 | Image(systemName: item.name) 23 | // Change SFSymbol styling based on state 24 | .font(state.isSelected ? .title2 : .body) 25 | .foregroundColor(state.isSelected ? Color(.systemRed) : Color(.label)) 26 | 27 | Text(item.name) 28 | // Change text styling based on state 29 | .font(Font.headline.weight(state.isSelected ? .bold : .regular)) 30 | .foregroundColor(state.isSelected ? Color(.systemRed) : Color(.label)) 31 | 32 | Spacer() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Handle User Interactions/UserInteractionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInteractionViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 13/08/2022. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class UserInteractionViewController: UIViewController { 12 | 13 | var collectionView: UICollectionView! 14 | 15 | let dataModel = [ 16 | SFSymbolItem(name: "applelogo"), 17 | SFSymbolItem(name: "iphone"), 18 | SFSymbolItem(name: "icloud"), 19 | SFSymbolItem(name: "icloud.fill"), 20 | SFSymbolItem(name: "car"), 21 | SFSymbolItem(name: "car.fill"), 22 | SFSymbolItem(name: "bus"), 23 | SFSymbolItem(name: "bus.fill"), 24 | SFSymbolItem(name: "flame"), 25 | SFSymbolItem(name: "flame.fill"), 26 | SFSymbolItem(name: "bolt"), 27 | SFSymbolItem(name: "bolt.fill") 28 | ] 29 | 30 | private var userInteractionCellRegistration: UICollectionView.CellRegistration! 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | title = "Handle User Interactions" 36 | 37 | // Create cell registration 38 | userInteractionCellRegistration = .init { [unowned self] cell, indexPath, item in 39 | 40 | cell.configurationUpdateHandler = { [unowned self] cell, state in 41 | cell.contentConfiguration = createHostingConfiguration(for: item, with: state) 42 | 43 | var newBgConfiguration = UIBackgroundConfiguration.listGroupedCell() 44 | newBgConfiguration.backgroundColor = .systemBackground 45 | cell.backgroundConfiguration = newBgConfiguration 46 | } 47 | } 48 | 49 | // Configure collection view using list layout 50 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped) 51 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig) 52 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout) 53 | collectionView.dataSource = self 54 | view = collectionView 55 | } 56 | 57 | /// Create a content configuration that host the `UserInteractionCell` 58 | private func createHostingConfiguration( 59 | for item: SFSymbolItem, 60 | with state: UICellConfigurationState 61 | ) -> UIHostingConfiguration { 62 | 63 | return UIHostingConfiguration { 64 | 65 | 66 | UserInteractionCell(item: item, state: state) 67 | .swipeActions(edge: .trailing, allowsFullSwipe: false) { 68 | Button(role: .destructive) { [unowned self] in 69 | showAlert(title: "Delete", message: item.name) 70 | } label: { 71 | Label("", systemImage: "trash") 72 | } 73 | } 74 | .swipeActions(edge: .leading) { 75 | 76 | Button("SHARE") { [unowned self] in 77 | showAlert(title: "Share", message: item.name) 78 | } 79 | .tint(.green) 80 | 81 | Button { [unowned self] in 82 | showAlert(title: "Favorite", message: item.name) 83 | } label: { 84 | Label("", systemImage: "star") 85 | } 86 | .tint(.yellow) 87 | } 88 | } 89 | } 90 | 91 | private func showAlert(title: String, message: String) { 92 | 93 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 94 | 95 | let positiveAction = UIAlertAction(title: "OK", style: .default) 96 | alert.addAction(positiveAction) 97 | 98 | present(alert, animated: true) 99 | } 100 | } 101 | 102 | extension UserInteractionViewController: UICollectionViewDataSource { 103 | 104 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 105 | return dataModel.count 106 | } 107 | 108 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 109 | 110 | let item = dataModel[indexPath.row] 111 | let cell = collectionView.dequeueConfiguredReusableCell(using: userInteractionCellRegistration, 112 | for: indexPath, 113 | item: item) 114 | 115 | return cell 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUICustomCellViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 06/08/2022. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | enum Section { 12 | case main 13 | } 14 | 15 | struct SFSymbolItem: Hashable { 16 | let name: String 17 | let image: UIImage 18 | 19 | init(name: String) { 20 | self.name = name 21 | self.image = UIImage(systemName: name)! 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Placeholder cell/DataProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataProvider.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 15/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DataProvider { 11 | 12 | /// Sample data for demo purpose 13 | private static let serverData = [ 14 | Quote(symbol: "person.2", content: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", author: "Martin Fowler"), 15 | Quote(symbol: "desktopcomputer", content: "First, solve the problem. Then, write the code.", author: "John Johnson"), 16 | Quote(symbol: "arrow.3.trianglepath", content: "Before software can be reusable it first has to be usable.", author: "Ralph Johnson"), 17 | Quote(symbol: "exclamationmark.triangle", content: "The best error message is the one that never shows up.", author: "Thomas Fuchs"), 18 | Quote(symbol: "brain.head.profile", content: "The only way to learn a new programming language is by writing programs in it.", author: "Dennis Ritchie"), 19 | Quote(symbol: "house", content: "Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live", author: "John Woods"), 20 | Quote(symbol: "chevron.left.forwardslash.chevron.right", content: "Truth can only be found in one place: the code.", author: " Robert C. Martin"), 21 | Quote(symbol: "hammer", content: "In some ways, programming is like painting. You start with a blank canvas and certain basic raw materials. You use a combination of science, art, and craft to determine what to do with them.", author: "Andrew Hunt"), 22 | Quote(symbol: "lightbulb", content: "Testing leads to failure, and failure leads to understanding.", author: "Burt Rutan"), 23 | Quote(symbol: "snowflake", content: "Walking on water and developing software from a specification are easy if both are frozen.", author: "Edward V. Berard"), 24 | ] 25 | 26 | /// Simulate fetching data from a remote server 27 | static func fetchData() async -> [Quote] { 28 | 29 | // Sleep for 2s to simulate a wait when fetching data from remote server 30 | try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) 31 | 32 | // return `serverData` shuffled 33 | return serverData.shuffled() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Placeholder cell/PlaceholderCellViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceholderCellViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 10/10/2022. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | struct Quote { 12 | let symbol: String 13 | let content: String 14 | let author: String 15 | } 16 | 17 | class PlaceholderCellViewController: UIViewController { 18 | 19 | /// Data to show in collection view 20 | private var collectionViewData = [Quote]() 21 | 22 | private var collectionView: UICollectionView! 23 | private var quoteCellRegistration: UICollectionView.CellRegistration! 24 | private let refreshControl = UIRefreshControl() 25 | private var isLoading = true 26 | 27 | /// Dummy data needed for placeholder cells 28 | /// Note: The content of `Quote` is not important, we just need some text to generate the placeholder UI. 29 | let dummyData = [ 30 | Quote(symbol: "iphone", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore", author: "Author name"), 31 | Quote(symbol: "iphone", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore", author: "Author name"), 32 | Quote(symbol: "iphone", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore", author: "Author name"), 33 | Quote(symbol: "iphone", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore", author: "Author name"), 34 | ] 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | // Define cell registration 40 | quoteCellRegistration = .init { cell, indexPath, item in 41 | 42 | let hostingConfiguration = UIHostingConfiguration { [unowned self] in 43 | QuoteCell(item: item, isLoading: self.isLoading) 44 | }.margins(.horizontal, 20) 45 | 46 | // Make hosting configuration as the cell's content configuration 47 | cell.contentConfiguration = hostingConfiguration 48 | } 49 | 50 | // Configure collection view using list layout 51 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .plain) 52 | layoutConfig.showsSeparators = false 53 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig) 54 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout) 55 | collectionView.dataSource = self 56 | view = collectionView 57 | 58 | // Setup refresh control 59 | refreshControl.addTarget(self, action: #selector(performPullToRefresh), for: .valueChanged) 60 | collectionView.addSubview(refreshControl) 61 | 62 | // Fetch data from data provider 63 | performPullToRefresh() 64 | } 65 | 66 | @objc func performPullToRefresh() { 67 | 68 | refreshControl.beginRefreshing() 69 | 70 | // Load the collection view with dummy data 71 | isLoading = true 72 | collectionViewData = dummyData 73 | collectionView.reloadData() 74 | 75 | Task { 76 | // Load the collection view with data from data provider 77 | collectionViewData = await DataProvider.fetchData() 78 | collectionView.reloadData() 79 | isLoading = false 80 | 81 | refreshControl.endRefreshing() 82 | } 83 | } 84 | 85 | } 86 | 87 | // MARK: - UICollectionViewDataSource 88 | extension PlaceholderCellViewController: UICollectionViewDataSource { 89 | 90 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 91 | return collectionViewData.count 92 | } 93 | 94 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 95 | 96 | let item = collectionViewData[indexPath.row] 97 | let cell = collectionView.dequeueConfiguredReusableCell( 98 | using: quoteCellRegistration, 99 | for: indexPath, 100 | item: item 101 | ) 102 | 103 | return cell 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Placeholder cell/PlaceholderModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceholderModifier.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 15/10/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PlaceholderModifier: ViewModifier { 11 | 12 | var isPlaceholder: Bool 13 | 14 | func body(content: Content) -> some View { 15 | if isPlaceholder { 16 | // Apply a redaction to current view 17 | content.redacted(reason: .placeholder) 18 | } else { 19 | // Do not apply any redaction to current view 20 | content 21 | } 22 | } 23 | } 24 | 25 | extension View { 26 | func showAsPlacehoder(_ isPlaceholder: Bool) -> some View { 27 | modifier(PlaceholderModifier(isPlaceholder: isPlaceholder)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Placeholder cell/QuoteCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuoteCell.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 15/10/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct QuoteCell: View { 11 | 12 | var item: Quote 13 | var isLoading: Bool 14 | 15 | var body: some View { 16 | 17 | VStack(spacing: 12) { 18 | Image(systemName: item.symbol) 19 | .imageScale(.large) 20 | .fontWeight(.bold) 21 | .padding() 22 | .background { 23 | Circle() 24 | .fill(Color(.secondarySystemFill)) 25 | } 26 | Text(item.content) 27 | .fontWeight(.bold) 28 | Text("- \(item.author)") 29 | .frame(maxWidth: .infinity, alignment: .trailing) 30 | .font(.footnote) 31 | .italic() 32 | } 33 | .frame(maxWidth: .infinity) 34 | .padding() 35 | .background { 36 | RoundedRectangle(cornerRadius: 12.0) 37 | .fill(Color(.systemFill)) 38 | } 39 | .showAsPlacehoder(isLoading) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Refresh Cells Content/TodoListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoListCell.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 05/09/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TodoListCell: View { 11 | 12 | @ObservedObject var item: TodoItem 13 | 14 | var body: some View { 15 | HStack { 16 | Text(item.title) 17 | .font(Font.title2) 18 | .frame(maxWidth: .infinity, alignment: .leading) 19 | .strikethrough(item.completed) 20 | .foregroundColor(item.completed ? .gray : .black) 21 | 22 | // Bind `Toggle` to `item` 23 | Toggle("", isOn: $item.completed.animation()) 24 | .frame(maxWidth: 50) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/Refresh Cells Content/TodoListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoListViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 05/09/2022. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class TodoItem: ObservableObject { 12 | 13 | let title: String 14 | @Published var completed: Bool 15 | 16 | init(_ title: String) { 17 | self.title = title 18 | self.completed = false 19 | } 20 | } 21 | 22 | 23 | class TodoListViewController: UIViewController { 24 | 25 | var collectionView: UICollectionView! 26 | private var todoListCellRegistration: UICollectionView.CellRegistration! 27 | 28 | let todoItems = [ 29 | TodoItem("Write a blog post"), 30 | TodoItem("Call John"), 31 | TodoItem("Make doctor's appointment"), 32 | TodoItem("Reply emails"), 33 | TodoItem("Buy Lego for Jimmy"), 34 | TodoItem("Get a hair cut"), 35 | TodoItem("Book flight to Japan"), 36 | ] 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | // Create cell registration 42 | todoListCellRegistration = .init { cell, indexPath, item in 43 | 44 | cell.contentConfiguration = UIHostingConfiguration { 45 | TodoListCell(item: item) 46 | } 47 | 48 | var newBgConfiguration = UIBackgroundConfiguration.listGroupedCell() 49 | newBgConfiguration.backgroundColor = .systemBackground 50 | cell.backgroundConfiguration = newBgConfiguration 51 | } 52 | 53 | // Configure collection view using list layout 54 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped) 55 | layoutConfig.showsSeparators = false 56 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig) 57 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout) 58 | collectionView.dataSource = self 59 | view = collectionView 60 | 61 | // Add "Complete All" button 62 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Complete All", 63 | style: .plain, 64 | target: self, 65 | action: #selector(completeAllTapped)) 66 | } 67 | 68 | @objc private func completeAllTapped() { 69 | // Update all elements to `completed = true` 70 | for item in todoItems { 71 | item.completed = true 72 | } 73 | } 74 | } 75 | 76 | extension TodoListViewController: UICollectionViewDataSource { 77 | 78 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 79 | return todoItems.count 80 | } 81 | 82 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 83 | 84 | let item = todoItems[indexPath.row] 85 | let cell = collectionView.dequeueConfiguredReusableCell( 86 | using: todoListCellRegistration, 87 | for: indexPath, 88 | item: item 89 | ) 90 | 91 | return cell 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 06/08/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/SwiftUI Custom Cell/MyFirstSwiftUICell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyFirstSwiftUICell.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 06/08/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MyFirstSwiftUICell: View { 11 | 12 | var item: SFSymbolItem 13 | 14 | var body: some View { 15 | 16 | HStack(alignment: .firstTextBaseline) { 17 | Image(systemName: item.name) 18 | .padding() 19 | Spacer() 20 | Text(item.name) 21 | .font(.system(.title3, weight: .semibold)) 22 | Spacer() 23 | Image(systemName: item.name) 24 | .padding() 25 | } 26 | .frame(height: 70) 27 | .background { 28 | RoundedRectangle(cornerRadius: 12.0) 29 | .fill(Color(.systemYellow)) 30 | } 31 | .alignmentGuide(.listRowSeparatorLeading) { $0[.leading] } 32 | .alignmentGuide(.listRowSeparatorTrailing) { $0[.trailing] } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/SwiftUI Custom Cell/SwiftUICustomCellViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUICustomCellViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 06/08/2022. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class SwiftUICustomCellViewController: UIViewController { 12 | 13 | var collectionView: UICollectionView! 14 | 15 | let dataModel = [ 16 | SFSymbolItem(name: "applelogo"), 17 | SFSymbolItem(name: "iphone"), 18 | SFSymbolItem(name: "message"), 19 | SFSymbolItem(name: "message.fill"), 20 | SFSymbolItem(name: "sun.min"), 21 | SFSymbolItem(name: "sun.min.fill"), 22 | SFSymbolItem(name: "sunset"), 23 | SFSymbolItem(name: "sunset.fill"), 24 | SFSymbolItem(name: "pencil"), 25 | SFSymbolItem(name: "pencil.circle"), 26 | SFSymbolItem(name: "highlighter"), 27 | SFSymbolItem(name: "network"), 28 | SFSymbolItem(name: "icloud"), 29 | SFSymbolItem(name: "icloud.fill"), 30 | SFSymbolItem(name: "car"), 31 | SFSymbolItem(name: "car.fill"), 32 | SFSymbolItem(name: "bus"), 33 | SFSymbolItem(name: "bus.fill"), 34 | SFSymbolItem(name: "flame"), 35 | SFSymbolItem(name: "flame.fill"), 36 | SFSymbolItem(name: "bolt"), 37 | SFSymbolItem(name: "bolt.fill") 38 | ] 39 | 40 | private var swiftUICellRegistration: UICollectionView.CellRegistration = { 41 | .init { cell, indexPath, item in 42 | 43 | let hostingConfiguration = UIHostingConfiguration { 44 | MyFirstSwiftUICell(item: item) 45 | }.margins(.horizontal, 50) 46 | 47 | // Make hosting configuration as the cell's content configuration 48 | cell.contentConfiguration = hostingConfiguration 49 | } 50 | }() 51 | 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | 55 | title = "SwiftUI Custom Cell" 56 | 57 | // Configure collection view using list layout 58 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped) 59 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig) 60 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout) 61 | collectionView.dataSource = self 62 | view = collectionView 63 | 64 | } 65 | } 66 | 67 | extension SwiftUICustomCellViewController: UICollectionViewDataSource { 68 | 69 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 70 | return dataModel.count 71 | } 72 | 73 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 74 | 75 | let item = dataModel[indexPath.row] 76 | let cell = collectionView.dequeueConfiguredReusableCell(using: swiftUICellRegistration, for: indexPath, item: item) 77 | 78 | return cell 79 | } 80 | } 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/UIKit Subview/AgreementView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AgreementView.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 09/04/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AgreementView: View { 11 | 12 | class ViewState: ObservableObject { 13 | @Published var isChecked = false 14 | } 15 | 16 | /// ObservableObject to keep track of the current check status 17 | @ObservedObject var currentViewState = ViewState() 18 | 19 | @State private var fontSize = 16.0 20 | @State private var isBold = false 21 | 22 | var body: some View { 23 | Text("I have read and agree to Apple's [User Agreement](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/) and [Privacy Policy](https://www.apple.com/legal/privacy/en-ww/)") 24 | .font(.system(size: fontSize)) 25 | .bold(isBold) 26 | .onReceive(currentViewState.$isChecked) { checked in 27 | // Animate based on the `isCheck` state 28 | withAnimation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 1)) { 29 | fontSize = checked ? 18.0 : 16.0 30 | isBold = checked 31 | } 32 | } 33 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) 34 | } 35 | 36 | func setIsChecked(_ isChecked: Bool) { 37 | // Update current `isCheck` state 38 | currentViewState.isChecked = isChecked 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/UIKit Subview/KSSwitchButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSSwitchButton.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 08/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class KSSwitchButton: UIButton { 11 | 12 | var switchButtonValueDidChanged: ((_ switchButton: KSSwitchButton, _ isSelected: Bool) -> Void)? 13 | 14 | @IBInspectable 15 | var onImage: UIImage? { 16 | didSet { 17 | setButton(image: onImage, for: .selected) 18 | } 19 | } 20 | 21 | @IBInspectable 22 | var offImage: UIImage? { 23 | didSet { 24 | setButton(image: offImage, for: .normal) 25 | } 26 | } 27 | 28 | override public init(frame: CGRect) { 29 | super.init(frame: frame) 30 | commonSetup() 31 | } 32 | 33 | required public init?(coder aDecoder: NSCoder) { 34 | super.init(coder: aDecoder) 35 | commonSetup() 36 | } 37 | 38 | } 39 | 40 | // MARK: - Public functions 41 | extension KSSwitchButton { 42 | 43 | func toggle() { 44 | self.isSelected.toggle() 45 | } 46 | } 47 | 48 | // MARK: - Private functions 49 | private extension KSSwitchButton { 50 | 51 | func commonSetup() { 52 | 53 | precondition(buttonType == .custom, "Button type must be 'custom'") 54 | 55 | setButton(image: offImage, for: .normal) 56 | setButton(image: onImage, for: .selected) 57 | 58 | // Override touch up inside event 59 | addTarget(self, action: #selector(touchUpInside(sender:)), for: .touchUpInside) 60 | } 61 | 62 | func setButton(image: UIImage?, for state: UIControl.State) { 63 | guard let image else { 64 | return 65 | } 66 | 67 | self.setImage(image, for: state) 68 | } 69 | 70 | @objc func touchUpInside(sender: KSSwitchButton) { 71 | self.isSelected.toggle() 72 | switchButtonValueDidChanged?(self, self.isSelected) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/UIKit Subview/Model/ChartStrideBy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2022 Swift Charts Examples. 3 | // Open Source - MIT License 4 | 5 | import Foundation 6 | import Charts 7 | 8 | enum ChartStrideBy: Identifiable, CaseIterable { 9 | case second 10 | case minute 11 | case hour 12 | case day 13 | case weekday 14 | case weekOfYear 15 | case month 16 | case year 17 | 18 | var id: String { title } 19 | 20 | var title: String { 21 | switch self { 22 | case .second: 23 | return "Second" 24 | case .minute: 25 | return "Minute" 26 | case .hour: 27 | return "Hour" 28 | case .day: 29 | return "Day" 30 | case .weekday: 31 | return "Weekday" 32 | case .weekOfYear: 33 | return "Week of Year" 34 | case .month: 35 | return "Month" 36 | case .year: 37 | return "Year" 38 | } 39 | } 40 | 41 | var time: Calendar.Component { 42 | switch self { 43 | case .second: 44 | return .second 45 | case .minute: 46 | return .minute 47 | case .hour: 48 | return .hour 49 | case .day: 50 | return .day 51 | case .weekday: 52 | return .weekday 53 | case .weekOfYear: 54 | return .weekOfYear 55 | case .month: 56 | return .month 57 | case .year: 58 | return .year 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/UIKit Subview/Model/ChartType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2022 Swift Charts Examples. 3 | // Open Source - MIT License 4 | 5 | import SwiftUI 6 | 7 | enum ChartCategory: String, CaseIterable, Hashable, Identifiable { 8 | case all 9 | case apple 10 | case line 11 | case bar 12 | case area 13 | case range 14 | case heatMap 15 | case point 16 | 17 | var id: String { self.rawValue } 18 | 19 | var sfSymbolName: String { 20 | switch self { 21 | case .all: 22 | return "" 23 | case .apple: 24 | return "applelogo" 25 | case .line: 26 | return "chart.xyaxis.line" 27 | case .bar: 28 | return "chart.bar.fill" 29 | case .area: 30 | return "triangle.fill" 31 | case .range: 32 | return "trapezoid.and.line.horizontal.fill" 33 | case .heatMap: 34 | return "checkerboard.rectangle" 35 | case .point: 36 | return "point.3.connected.trianglepath.dotted" 37 | } 38 | } 39 | } 40 | 41 | enum ChartType: String, Identifiable, CaseIterable { 42 | // Apple 43 | case heartBeat 44 | case oneDimensionalBar 45 | case screenTime 46 | 47 | // Line Charts 48 | case singleLine 49 | case singleLineLollipop 50 | case animatingLine 51 | case gradientLine 52 | case multiLine 53 | case linePoint 54 | 55 | // Bar Charts 56 | case singleBar 57 | case singleBarThreshold 58 | case twoBars 59 | case pyramid 60 | case timeSheetBar 61 | #if canImport(UIKit) 62 | case soundBar 63 | #endif 64 | case scrollingBar 65 | 66 | // Area Charts 67 | case areaSimple 68 | case stackedArea 69 | 70 | // Range Charts 71 | case rangeSimple 72 | case rangeHeartRate 73 | case candleStick 74 | 75 | // HeatMap Charts 76 | case customizeableHeatMap 77 | case gitContributions 78 | 79 | // Point Charts 80 | case scatter 81 | case vectorField 82 | 83 | var id: String { self.rawValue } 84 | 85 | var title: String { 86 | switch self { 87 | case .heartBeat: 88 | return "Electrocardiograms (ECG)" 89 | case .oneDimensionalBar: 90 | return "iPhone Storage" 91 | case .screenTime: 92 | return "Screen Time" 93 | case .singleLine: 94 | return "Line Chart" 95 | case .singleLineLollipop: 96 | return "Line Chart with Lollipop" 97 | case .animatingLine: 98 | return "Animating Line" 99 | case .gradientLine: 100 | return "Line with changing gradient" 101 | case .multiLine: 102 | return "Line Charts" 103 | case .linePoint: 104 | return "Line Point" 105 | case .singleBar: 106 | return "Single Bar" 107 | case .singleBarThreshold: 108 | return "Single Bar with Threshold Rule Mark" 109 | case .twoBars: 110 | return "Two Bars" 111 | case .scrollingBar: 112 | return "Horizontal Scrolling Bar Chart" 113 | case .pyramid: 114 | return "Pyramid" 115 | case .timeSheetBar: 116 | return "Time Sheet Bar" 117 | #if canImport(UIKit) 118 | case .soundBar: 119 | return "Sound Bar" 120 | #endif 121 | case .areaSimple: 122 | return "Area Chart" 123 | case .stackedArea: 124 | return "Stacked Area Chart" 125 | case .rangeSimple: 126 | return "Range Chart" 127 | case .rangeHeartRate: 128 | return "Heart Rate Range Chart" 129 | case .candleStick: 130 | return "Candle Stick Chart" 131 | case .customizeableHeatMap: 132 | return "Customizable Heat Map" 133 | case .gitContributions: 134 | return "GitHub Contributions Graph" 135 | case .scatter: 136 | return "Scatter Chart" 137 | case .vectorField: 138 | return "Vector Field" 139 | } 140 | } 141 | 142 | var category: ChartCategory { 143 | switch self { 144 | case .heartBeat, .screenTime, .oneDimensionalBar: 145 | return .apple 146 | case .singleLine, .singleLineLollipop, .animatingLine, .gradientLine, .multiLine, .linePoint: 147 | return .line 148 | case .singleBar, .singleBarThreshold, .twoBars, .pyramid, .timeSheetBar: 149 | return .bar 150 | #if canImport(UIKit) 151 | case .soundBar: 152 | return .bar 153 | #endif 154 | case .areaSimple, .stackedArea: 155 | return .area 156 | case .rangeSimple, .rangeHeartRate, .candleStick: 157 | return .range 158 | case .customizeableHeatMap, .gitContributions: 159 | return .heatMap 160 | case .scatter, .vectorField: 161 | return .point 162 | case .scrollingBar: 163 | return .bar 164 | } 165 | } 166 | 167 | var view: some View { 168 | overviewOrDetailView(isOverview: true) 169 | } 170 | 171 | var chartDescriptor: AXChartDescriptor? { 172 | // This is necessary since we use images for preview/overview 173 | // TODO: Use protocol conformance to remove manual switch necessity 174 | switch self { 175 | case .singleLine: 176 | return SingleLine(isOverview: true).makeChartDescriptor() 177 | case .singleLineLollipop: 178 | return SingleLineLollipop(isOverview: true).makeChartDescriptor() 179 | case .multiLine: 180 | return MultiLine(isOverview: true).makeChartDescriptor() 181 | case .heartBeat: 182 | return HeartBeat(isOverview: true).makeChartDescriptor() 183 | case .animatingLine: 184 | return AnimatedChart(x: 0, isOverview: true).makeChartDescriptor() 185 | case .singleBar: 186 | return SingleBar(isOverview: true).makeChartDescriptor() 187 | case .singleBarThreshold: 188 | return SingleBarThreshold(isOverview: true).makeChartDescriptor() 189 | case .twoBars: 190 | return TwoBars(isOverview: true).makeChartDescriptor() 191 | case .oneDimensionalBar: 192 | return OneDimensionalBar(isOverview: true).makeChartDescriptor() 193 | case .candleStick: 194 | return CandleStickChart(isOverview: true).makeChartDescriptor() 195 | case .timeSheetBar: 196 | return TimeSheetBar(isOverview: true).makeChartDescriptor() 197 | case .pyramid: 198 | return PyramidChart(isOverview: true).makeChartDescriptor() 199 | case .areaSimple: 200 | return AreaSimple(isOverview: true).makeChartDescriptor() 201 | case .stackedArea: 202 | return StackedArea(isOverview: true).makeChartDescriptor() 203 | case .rangeSimple: 204 | return RangeSimple(isOverview: true).makeChartDescriptor() 205 | case .rangeHeartRate: 206 | return HeartRateRangeChart(isOverview: true).makeChartDescriptor() 207 | case .customizeableHeatMap: 208 | return HeatMap(isOverview: true).makeChartDescriptor() 209 | case .scatter: 210 | return ScatterChart(isOverview: true).makeChartDescriptor() 211 | case .vectorField: 212 | return VectorField(isOverview: true).makeChartDescriptor() 213 | case .gradientLine: 214 | return GradientLine(isOverview: true).makeChartDescriptor() 215 | #if canImport(UIKit) 216 | case .soundBar: 217 | return SoundBars(isOverview: true).makeChartDescriptor() 218 | #endif 219 | case .linePoint: 220 | return LinePlot(isOverview: true).makeChartDescriptor() 221 | case .gitContributions: 222 | return GitHubContributionsGraph(isOverview: true).makeChartDescriptor() 223 | case .screenTime: 224 | // This graph mirrors Apple's implementation 225 | // each individual sub graph has it's own audio graph/descriptor 226 | fallthrough 227 | default: 228 | return nil 229 | 230 | } 231 | } 232 | 233 | var detailView: some View { 234 | overviewOrDetailView(isOverview: false) 235 | } 236 | 237 | @ViewBuilder 238 | private func overviewOrDetailView(isOverview: Bool) -> some View { 239 | switch self { 240 | case .oneDimensionalBar: 241 | OneDimensionalBar(isOverview: isOverview) 242 | case .screenTime: 243 | ScreenTime(isOverview: isOverview) 244 | case .singleLine: 245 | SingleLine(isOverview: isOverview) 246 | case .singleLineLollipop: 247 | SingleLineLollipop(isOverview: isOverview) 248 | case .heartBeat: 249 | HeartBeat(isOverview: isOverview) 250 | case .animatingLine: 251 | AnimatingLine(isOverview: isOverview) 252 | case .gradientLine: 253 | GradientLine(isOverview: isOverview) 254 | case .multiLine: 255 | MultiLine(isOverview: isOverview) 256 | case .linePoint: 257 | LinePlot(isOverview: isOverview) 258 | case .singleBar: 259 | SingleBar(isOverview: isOverview) 260 | case .scrollingBar: 261 | ScrollingBar(isOverview: isOverview) 262 | case .singleBarThreshold: 263 | SingleBarThreshold(isOverview: isOverview) 264 | case .twoBars: 265 | TwoBars(isOverview: isOverview) 266 | case .pyramid: 267 | PyramidChart(isOverview: isOverview) 268 | case .timeSheetBar: 269 | TimeSheetBar(isOverview: isOverview) 270 | #if canImport(UIKit) 271 | case .soundBar: 272 | SoundBars(isOverview: isOverview) 273 | #endif 274 | case .areaSimple: 275 | AreaSimple(isOverview: isOverview) 276 | case .stackedArea: 277 | StackedArea(isOverview: isOverview) 278 | case .rangeSimple: 279 | RangeSimple(isOverview: isOverview) 280 | case .rangeHeartRate: 281 | HeartRateRangeChart(isOverview: isOverview) 282 | case .candleStick: 283 | CandleStickChart(isOverview: isOverview) 284 | case .customizeableHeatMap: 285 | HeatMap(isOverview: isOverview) 286 | case .gitContributions: 287 | GitHubContributionsGraph(isOverview: isOverview) 288 | case .scatter: 289 | ScatterChart(isOverview: isOverview) 290 | case .vectorField: 291 | VectorField(isOverview: isOverview) 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/UIKit Subview/Model/InterpolationMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2022 Swift Charts Examples. 3 | // Open Source - MIT License 4 | 5 | import Foundation 6 | import Charts 7 | 8 | enum ChartInterpolationMethod: Identifiable, CaseIterable { 9 | case linear 10 | case monotone 11 | case catmullRom 12 | case cardinal 13 | case stepStart 14 | case stepCenter 15 | case stepEnd 16 | 17 | var id: String { mode.description } 18 | 19 | var mode: InterpolationMethod { 20 | switch self { 21 | case .linear: 22 | return .linear 23 | case .monotone: 24 | return .monotone 25 | case .stepStart: 26 | return .stepStart 27 | case .stepCenter: 28 | return .stepCenter 29 | case .stepEnd: 30 | return .stepEnd 31 | case .catmullRom: 32 | return .catmullRom 33 | case .cardinal: 34 | return .cardinal 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Swift-Senpai-UICollectionView-SwiftUI/UIKit Subview/UIKitSubviewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitSubviewViewController.swift 3 | // Swift-Senpai-UICollectionView-SwiftUI 4 | // 5 | // Created by Kah Seng Lee on 08/04/2023. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class UIKitSubviewViewController: UIViewController { 12 | 13 | @IBOutlet weak var checkbox: KSSwitchButton! 14 | @IBOutlet weak var textContainerView: UIView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // Configure checkbox 20 | checkbox.onImage = UIImage(systemName: "checkmark.square") 21 | checkbox.offImage = UIImage(systemName: "square") 22 | 23 | // Configure `agreementView` 24 | let agreementView = AgreementView() 25 | checkbox.switchButtonValueDidChanged = { (_, isSelected) in 26 | // Update `agreementView` check status 27 | agreementView.setIsChecked(isSelected) 28 | } 29 | 30 | // Convert `agreementView` to UIView 31 | let config = UIHostingConfiguration { 32 | agreementView 33 | } 34 | let subview = config.makeContentView() 35 | 36 | // Add `agreementView` into a container view 37 | textContainerView.addSubview(subview) 38 | 39 | textContainerView.translatesAutoresizingMaskIntoConstraints = false 40 | subview.translatesAutoresizingMaskIntoConstraints = false 41 | 42 | NSLayoutConstraint.activate([ 43 | textContainerView.topAnchor.constraint(equalTo: subview.topAnchor), 44 | textContainerView.bottomAnchor.constraint(equalTo: subview.bottomAnchor), 45 | textContainerView.leadingAnchor.constraint(equalTo: subview.leadingAnchor), 46 | textContainerView.trailingAnchor.constraint(equalTo: subview.trailingAnchor), 47 | ]) 48 | } 49 | } 50 | --------------------------------------------------------------------------------