├── .gitignore ├── BBSwiftUIKit.podspec ├── BBSwiftUIKit.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── BBSwiftUIKit ├── BBSwiftUIKit.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── BBSwiftUIKit │ ├── BBPageControl.swift │ ├── BBScrollView.swift │ ├── BBSwiftUIKit.h │ ├── BBTableView.swift │ └── Info.plist ├── BBSwiftUIKitDemo ├── BBSwiftUIKitDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── BBSwiftUIKitDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── CycleView.swift │ ├── CycleViewExample.swift │ ├── Info.plist │ ├── PageControlExample.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── SceneDelegate.swift │ ├── ScrollViewExample.swift │ ├── ScrollViewExample2.swift │ ├── ScrollViewExample3.swift │ ├── ScrollViewExample4.swift │ └── TableViewExample.swift ├── LICENSE ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | .swiftpm 43 | 44 | # CocoaPods 45 | 46 | Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots/**/*.png 65 | fastlane/test_output 66 | 67 | # Code Injection 68 | # 69 | # After new code Injection tools there's a generated folder /iOSInjectionProject 70 | # https://github.com/johnno1962/injectionforxcode 71 | 72 | iOSInjectionProject/ 73 | 74 | # macOS 75 | 76 | .DS_Store 77 | -------------------------------------------------------------------------------- /BBSwiftUIKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'BBSwiftUIKit' 4 | s.version = '0.1.0' 5 | s.summary = 'A SwiftUI library with powerful UIKit features.' 6 | 7 | s.description = <<-DESC 8 | Get and set scroll view `contentOffset`. 9 | Set scroll view `isPagingEnabled`, `bounces` and other properties. 10 | Pull down or up table view to refresh data or load more data. 11 | Reload table view data or rows. 12 | Scroll table view to the specific row. 13 | Get and set page control `currentPage`. 14 | DESC 15 | 16 | s.homepage = 'https://github.com/Silence-GitHub/BBSwiftUIKit' 17 | 18 | s.license = { :type => 'MIT', :file => 'LICENSE' } 19 | 20 | s.author = { 'Kaibo Lu' => 'lukaibolkb@gmail.com' } 21 | 22 | s.platform = :ios, '13.0' 23 | 24 | s.swift_version = '5.0' 25 | 26 | s.source = { :git => 'https://github.com/Silence-GitHub/BBSwiftUIKit.git', :tag => s.version } 27 | 28 | s.requires_arc = true 29 | 30 | s.source_files = 'BBSwiftUIKit/BBSwiftUIKit/*.{h,swift}' 31 | 32 | end 33 | -------------------------------------------------------------------------------- /BBSwiftUIKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /BBSwiftUIKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4A140AE1246B302F00863A16 /* BBSwiftUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A140ADF246B302F00863A16 /* BBSwiftUIKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 4A140AE8246B305700863A16 /* BBScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140AE7246B305700863A16 /* BBScrollView.swift */; }; 12 | 4A140B3B246B6CC900863A16 /* BBPageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B3A246B6CC900863A16 /* BBPageControl.swift */; }; 13 | 4A9245E9246E0C3200BA48AF /* BBTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9245E8246E0C3200BA48AF /* BBTableView.swift */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | 4A140ADC246B302F00863A16 /* BBSwiftUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BBSwiftUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | 4A140ADF246B302F00863A16 /* BBSwiftUIKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BBSwiftUIKit.h; sourceTree = ""; }; 19 | 4A140AE0246B302F00863A16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 20 | 4A140AE7246B305700863A16 /* BBScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BBScrollView.swift; sourceTree = ""; }; 21 | 4A140B3A246B6CC900863A16 /* BBPageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BBPageControl.swift; sourceTree = ""; }; 22 | 4A9245E8246E0C3200BA48AF /* BBTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BBTableView.swift; sourceTree = ""; }; 23 | /* End PBXFileReference section */ 24 | 25 | /* Begin PBXFrameworksBuildPhase section */ 26 | 4A140AD9246B302F00863A16 /* Frameworks */ = { 27 | isa = PBXFrameworksBuildPhase; 28 | buildActionMask = 2147483647; 29 | files = ( 30 | ); 31 | runOnlyForDeploymentPostprocessing = 0; 32 | }; 33 | /* End PBXFrameworksBuildPhase section */ 34 | 35 | /* Begin PBXGroup section */ 36 | 4A140AD2246B302F00863A16 = { 37 | isa = PBXGroup; 38 | children = ( 39 | 4A140ADE246B302F00863A16 /* BBSwiftUIKit */, 40 | 4A140ADD246B302F00863A16 /* Products */, 41 | ); 42 | sourceTree = ""; 43 | }; 44 | 4A140ADD246B302F00863A16 /* Products */ = { 45 | isa = PBXGroup; 46 | children = ( 47 | 4A140ADC246B302F00863A16 /* BBSwiftUIKit.framework */, 48 | ); 49 | name = Products; 50 | sourceTree = ""; 51 | }; 52 | 4A140ADE246B302F00863A16 /* BBSwiftUIKit */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 4A140ADF246B302F00863A16 /* BBSwiftUIKit.h */, 56 | 4A140AE7246B305700863A16 /* BBScrollView.swift */, 57 | 4A140B3A246B6CC900863A16 /* BBPageControl.swift */, 58 | 4A9245E8246E0C3200BA48AF /* BBTableView.swift */, 59 | 4A140AE0246B302F00863A16 /* Info.plist */, 60 | ); 61 | path = BBSwiftUIKit; 62 | sourceTree = ""; 63 | }; 64 | /* End PBXGroup section */ 65 | 66 | /* Begin PBXHeadersBuildPhase section */ 67 | 4A140AD7246B302F00863A16 /* Headers */ = { 68 | isa = PBXHeadersBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | 4A140AE1246B302F00863A16 /* BBSwiftUIKit.h in Headers */, 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXHeadersBuildPhase section */ 76 | 77 | /* Begin PBXNativeTarget section */ 78 | 4A140ADB246B302F00863A16 /* BBSwiftUIKit */ = { 79 | isa = PBXNativeTarget; 80 | buildConfigurationList = 4A140AE4246B302F00863A16 /* Build configuration list for PBXNativeTarget "BBSwiftUIKit" */; 81 | buildPhases = ( 82 | 4A140AD7246B302F00863A16 /* Headers */, 83 | 4A140AD8246B302F00863A16 /* Sources */, 84 | 4A140AD9246B302F00863A16 /* Frameworks */, 85 | 4A140ADA246B302F00863A16 /* Resources */, 86 | ); 87 | buildRules = ( 88 | ); 89 | dependencies = ( 90 | ); 91 | name = BBSwiftUIKit; 92 | productName = BBSwiftUIKit; 93 | productReference = 4A140ADC246B302F00863A16 /* BBSwiftUIKit.framework */; 94 | productType = "com.apple.product-type.framework"; 95 | }; 96 | /* End PBXNativeTarget section */ 97 | 98 | /* Begin PBXProject section */ 99 | 4A140AD3246B302F00863A16 /* Project object */ = { 100 | isa = PBXProject; 101 | attributes = { 102 | LastUpgradeCheck = 1130; 103 | ORGANIZATIONNAME = "Kaibo Lu"; 104 | TargetAttributes = { 105 | 4A140ADB246B302F00863A16 = { 106 | CreatedOnToolsVersion = 11.3; 107 | LastSwiftMigration = 1130; 108 | }; 109 | }; 110 | }; 111 | buildConfigurationList = 4A140AD6246B302F00863A16 /* Build configuration list for PBXProject "BBSwiftUIKit" */; 112 | compatibilityVersion = "Xcode 9.3"; 113 | developmentRegion = en; 114 | hasScannedForEncodings = 0; 115 | knownRegions = ( 116 | en, 117 | Base, 118 | ); 119 | mainGroup = 4A140AD2246B302F00863A16; 120 | productRefGroup = 4A140ADD246B302F00863A16 /* Products */; 121 | projectDirPath = ""; 122 | projectRoot = ""; 123 | targets = ( 124 | 4A140ADB246B302F00863A16 /* BBSwiftUIKit */, 125 | ); 126 | }; 127 | /* End PBXProject section */ 128 | 129 | /* Begin PBXResourcesBuildPhase section */ 130 | 4A140ADA246B302F00863A16 /* Resources */ = { 131 | isa = PBXResourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXResourcesBuildPhase section */ 138 | 139 | /* Begin PBXSourcesBuildPhase section */ 140 | 4A140AD8246B302F00863A16 /* Sources */ = { 141 | isa = PBXSourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 4A140B3B246B6CC900863A16 /* BBPageControl.swift in Sources */, 145 | 4A9245E9246E0C3200BA48AF /* BBTableView.swift in Sources */, 146 | 4A140AE8246B305700863A16 /* BBScrollView.swift in Sources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXSourcesBuildPhase section */ 151 | 152 | /* Begin XCBuildConfiguration section */ 153 | 4A140AE2246B302F00863A16 /* Debug */ = { 154 | isa = XCBuildConfiguration; 155 | buildSettings = { 156 | ALWAYS_SEARCH_USER_PATHS = NO; 157 | CLANG_ANALYZER_NONNULL = YES; 158 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 159 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 160 | CLANG_CXX_LIBRARY = "libc++"; 161 | CLANG_ENABLE_MODULES = YES; 162 | CLANG_ENABLE_OBJC_ARC = YES; 163 | CLANG_ENABLE_OBJC_WEAK = YES; 164 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 165 | CLANG_WARN_BOOL_CONVERSION = YES; 166 | CLANG_WARN_COMMA = YES; 167 | CLANG_WARN_CONSTANT_CONVERSION = YES; 168 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 169 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 170 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 171 | CLANG_WARN_EMPTY_BODY = YES; 172 | CLANG_WARN_ENUM_CONVERSION = YES; 173 | CLANG_WARN_INFINITE_RECURSION = YES; 174 | CLANG_WARN_INT_CONVERSION = YES; 175 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 176 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 177 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 178 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 179 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 180 | CLANG_WARN_STRICT_PROTOTYPES = YES; 181 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 182 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 183 | CLANG_WARN_UNREACHABLE_CODE = YES; 184 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 185 | COPY_PHASE_STRIP = NO; 186 | CURRENT_PROJECT_VERSION = 1; 187 | DEBUG_INFORMATION_FORMAT = dwarf; 188 | ENABLE_STRICT_OBJC_MSGSEND = YES; 189 | ENABLE_TESTABILITY = YES; 190 | GCC_C_LANGUAGE_STANDARD = gnu11; 191 | GCC_DYNAMIC_NO_PIC = NO; 192 | GCC_NO_COMMON_BLOCKS = YES; 193 | GCC_OPTIMIZATION_LEVEL = 0; 194 | GCC_PREPROCESSOR_DEFINITIONS = ( 195 | "DEBUG=1", 196 | "$(inherited)", 197 | ); 198 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 199 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 200 | GCC_WARN_UNDECLARED_SELECTOR = YES; 201 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 202 | GCC_WARN_UNUSED_FUNCTION = YES; 203 | GCC_WARN_UNUSED_VARIABLE = YES; 204 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 205 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 206 | MTL_FAST_MATH = YES; 207 | ONLY_ACTIVE_ARCH = YES; 208 | SDKROOT = iphoneos; 209 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 210 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 211 | VERSIONING_SYSTEM = "apple-generic"; 212 | VERSION_INFO_PREFIX = ""; 213 | }; 214 | name = Debug; 215 | }; 216 | 4A140AE3246B302F00863A16 /* Release */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | ALWAYS_SEARCH_USER_PATHS = NO; 220 | CLANG_ANALYZER_NONNULL = YES; 221 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 223 | CLANG_CXX_LIBRARY = "libc++"; 224 | CLANG_ENABLE_MODULES = YES; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_ENABLE_OBJC_WEAK = YES; 227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_COMMA = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 243 | CLANG_WARN_STRICT_PROTOTYPES = YES; 244 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 245 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 246 | CLANG_WARN_UNREACHABLE_CODE = YES; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | COPY_PHASE_STRIP = NO; 249 | CURRENT_PROJECT_VERSION = 1; 250 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 251 | ENABLE_NS_ASSERTIONS = NO; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu11; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 262 | MTL_ENABLE_DEBUG_INFO = NO; 263 | MTL_FAST_MATH = YES; 264 | SDKROOT = iphoneos; 265 | SWIFT_COMPILATION_MODE = wholemodule; 266 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 267 | VALIDATE_PRODUCT = YES; 268 | VERSIONING_SYSTEM = "apple-generic"; 269 | VERSION_INFO_PREFIX = ""; 270 | }; 271 | name = Release; 272 | }; 273 | 4A140AE5246B302F00863A16 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | CLANG_ENABLE_MODULES = YES; 277 | CODE_SIGN_STYLE = Automatic; 278 | DEFINES_MODULE = YES; 279 | DYLIB_COMPATIBILITY_VERSION = 1; 280 | DYLIB_CURRENT_VERSION = 1; 281 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 282 | INFOPLIST_FILE = BBSwiftUIKit/Info.plist; 283 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 284 | LD_RUNPATH_SEARCH_PATHS = ( 285 | "$(inherited)", 286 | "@executable_path/Frameworks", 287 | "@loader_path/Frameworks", 288 | ); 289 | PRODUCT_BUNDLE_IDENTIFIER = Kaibo.BBSwiftUIKit; 290 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 291 | SKIP_INSTALL = YES; 292 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 293 | SWIFT_VERSION = 5.0; 294 | TARGETED_DEVICE_FAMILY = "1,2"; 295 | }; 296 | name = Debug; 297 | }; 298 | 4A140AE6246B302F00863A16 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | CLANG_ENABLE_MODULES = YES; 302 | CODE_SIGN_STYLE = Automatic; 303 | DEFINES_MODULE = YES; 304 | DYLIB_COMPATIBILITY_VERSION = 1; 305 | DYLIB_CURRENT_VERSION = 1; 306 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 307 | INFOPLIST_FILE = BBSwiftUIKit/Info.plist; 308 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 309 | LD_RUNPATH_SEARCH_PATHS = ( 310 | "$(inherited)", 311 | "@executable_path/Frameworks", 312 | "@loader_path/Frameworks", 313 | ); 314 | PRODUCT_BUNDLE_IDENTIFIER = Kaibo.BBSwiftUIKit; 315 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 316 | SKIP_INSTALL = YES; 317 | SWIFT_VERSION = 5.0; 318 | TARGETED_DEVICE_FAMILY = "1,2"; 319 | }; 320 | name = Release; 321 | }; 322 | /* End XCBuildConfiguration section */ 323 | 324 | /* Begin XCConfigurationList section */ 325 | 4A140AD6246B302F00863A16 /* Build configuration list for PBXProject "BBSwiftUIKit" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | 4A140AE2246B302F00863A16 /* Debug */, 329 | 4A140AE3246B302F00863A16 /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | 4A140AE4246B302F00863A16 /* Build configuration list for PBXNativeTarget "BBSwiftUIKit" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | 4A140AE5246B302F00863A16 /* Debug */, 338 | 4A140AE6246B302F00863A16 /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | /* End XCConfigurationList section */ 344 | }; 345 | rootObject = 4A140AD3246B302F00863A16 /* Project object */; 346 | } 347 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit/BBPageControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBPageControl.swift 3 | // BBSwiftUIKit 4 | // 5 | // Created by Kaibo Lu on 5/12/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public extension BBPageControl { 12 | func bb_hidesForSinglePage(_ hidesForSinglePage: Bool) -> BBPageControl { 13 | var view = self 14 | view.hidesForSinglePage = hidesForSinglePage 15 | return view 16 | } 17 | 18 | func bb_pageIndicatorTintColor(_ pageIndicatorTintColor: UIColor?) -> BBPageControl { 19 | var view = self 20 | view.pageIndicatorTintColor = pageIndicatorTintColor 21 | return view 22 | } 23 | 24 | func bb_currentPageIndicatorTintColor(_ currentPageIndicatorTintColor: UIColor?) -> BBPageControl { 25 | var view = self 26 | view.currentPageIndicatorTintColor = currentPageIndicatorTintColor 27 | return view 28 | } 29 | } 30 | 31 | public struct BBPageControl: UIViewRepresentable { 32 | @Binding var currentPage: Int 33 | var numberOfPages: Int 34 | var hidesForSinglePage: Bool = false 35 | var pageIndicatorTintColor: UIColor? 36 | var currentPageIndicatorTintColor: UIColor? 37 | 38 | public init(currentPage: Binding, numberOfPages: Int) { 39 | self._currentPage = currentPage 40 | self.numberOfPages = numberOfPages 41 | } 42 | 43 | public func makeUIView(context: Context) -> UIPageControl { 44 | let control = UIPageControl() 45 | control.addTarget(context.coordinator, action: #selector(Coordinator.updateCurrentPage(_:)), for: .valueChanged) 46 | return control 47 | } 48 | 49 | public func updateUIView(_ control: UIPageControl, context: Context) { 50 | control.numberOfPages = numberOfPages 51 | control.currentPage = currentPage // Set `currentPage` after `numberOfPages` 52 | control.hidesForSinglePage = hidesForSinglePage 53 | control.pageIndicatorTintColor = pageIndicatorTintColor 54 | control.currentPageIndicatorTintColor = currentPageIndicatorTintColor 55 | } 56 | 57 | public func makeCoordinator() -> Coordinator { Coordinator(self) } 58 | 59 | public class Coordinator: NSObject { 60 | let parent: BBPageControl 61 | 62 | init(_ view: BBPageControl) { parent = view } 63 | 64 | @objc func updateCurrentPage(_ sender: UIPageControl) { 65 | parent.currentPage = sender.currentPage 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit/BBScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBScrollView.swift 3 | // BBSwiftUIKit 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public protocol BBUIScrollViewRepresentable { 12 | var contentOffset: CGPoint { get set } 13 | var contentOffsetToScrollAnimated: CGPoint? { get set } 14 | var isPagingEnabled: Bool { get set } 15 | var bounces: Bool { get set } 16 | var alwaysBounceVertical: Bool { get set } 17 | var alwaysBounceHorizontal: Bool { get set } 18 | var showsVerticalScrollIndicator: Bool { get set } 19 | var showsHorizontalScrollIndicator: Bool { get set } 20 | 21 | func updateScrollView(_ scrollView: UIScrollView) 22 | } 23 | 24 | public extension CGPoint { 25 | static let bb_invalidContentOffset = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude) 26 | } 27 | 28 | public extension BBUIScrollViewRepresentable { 29 | func bb_isPagingEnabled(_ isPagingEnabled: Bool) -> Self { 30 | var view = self 31 | view.isPagingEnabled = isPagingEnabled 32 | return view 33 | } 34 | 35 | func bb_bounces(_ bounces: Bool) -> Self { 36 | var view = self 37 | view.bounces = bounces 38 | return view 39 | } 40 | 41 | func bb_alwaysBounceVertical(_ alwaysBounceVertical: Bool) -> Self { 42 | var view = self 43 | view.alwaysBounceVertical = alwaysBounceVertical 44 | return view 45 | } 46 | 47 | func bb_alwaysBounceHorizontal(_ alwaysBounceHorizontal: Bool) -> Self { 48 | var view = self 49 | view.alwaysBounceHorizontal = alwaysBounceHorizontal 50 | return view 51 | } 52 | 53 | func bb_showsVerticalScrollIndicator(_ showsVerticalScrollIndicator: Bool) -> Self { 54 | var view = self 55 | view.showsVerticalScrollIndicator = showsVerticalScrollIndicator 56 | return view 57 | } 58 | 59 | func bb_showsHorizontalScrollIndicator(_ showsHorizontalScrollIndicator: Bool) -> Self { 60 | var view = self 61 | view.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator 62 | return view 63 | } 64 | 65 | func updateScrollView(_ scrollView: UIScrollView) { 66 | scrollView.isPagingEnabled = isPagingEnabled 67 | scrollView.bounces = bounces 68 | scrollView.alwaysBounceVertical = alwaysBounceVertical 69 | scrollView.alwaysBounceHorizontal = alwaysBounceHorizontal 70 | scrollView.showsVerticalScrollIndicator = showsVerticalScrollIndicator 71 | scrollView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator 72 | } 73 | } 74 | 75 | public extension BBScrollView { 76 | func bb_contentOffset(_ contentOffset: Binding) -> Self { 77 | var view = self 78 | view._contentOffset = contentOffset 79 | return view 80 | } 81 | 82 | func bb_contentOffsetToScrollAnimated(_ contentOffsetToScrollAnimated: Binding) -> Self { 83 | var view = self 84 | view._contentOffsetToScrollAnimated = contentOffsetToScrollAnimated 85 | return view 86 | } 87 | } 88 | 89 | public struct BBScrollView: UIViewRepresentable, BBUIScrollViewRepresentable { 90 | public let axis: Axis.Set 91 | @Binding public var contentOffset: CGPoint 92 | @Binding public var contentOffsetToScrollAnimated: CGPoint? 93 | public var isPagingEnabled: Bool = false 94 | public var bounces: Bool = true 95 | public var alwaysBounceVertical: Bool = false 96 | public var alwaysBounceHorizontal: Bool = false 97 | public var showsVerticalScrollIndicator: Bool = true 98 | public var showsHorizontalScrollIndicator: Bool = true 99 | public let content: () -> Content 100 | 101 | public init(_ axis: Axis.Set, 102 | contentOffset: Binding = .constant(.bb_invalidContentOffset), 103 | @ViewBuilder content: @escaping () -> Content) 104 | { 105 | self.axis = axis 106 | self._contentOffset = contentOffset 107 | self._contentOffsetToScrollAnimated = .constant(nil) 108 | self.content = content 109 | } 110 | 111 | public func makeUIView(context: Context) -> UIScrollView { 112 | let scrollView = _BBScrollView(axis) 113 | scrollView.delegate = context.coordinator 114 | 115 | let host = UIHostingController(rootView: content()) 116 | host.view.translatesAutoresizingMaskIntoConstraints = false 117 | context.coordinator.host = host 118 | 119 | scrollView.addSubview(host.view) 120 | 121 | if axis.contains(.horizontal) && axis.contains(.vertical) { 122 | NSLayoutConstraint.activate([ 123 | scrollView.leftAnchor.constraint(equalTo: host.view.leftAnchor), 124 | scrollView.rightAnchor.constraint(equalTo: host.view.rightAnchor), 125 | scrollView.topAnchor.constraint(equalTo: host.view.topAnchor), 126 | scrollView.bottomAnchor.constraint(equalTo: host.view.bottomAnchor), 127 | ]) 128 | } else if axis.contains(.horizontal) { 129 | NSLayoutConstraint.activate([ scrollView.centerYAnchor.constraint(equalTo: host.view.centerYAnchor) ]) 130 | scrollView.setContentHuggingPriority(.defaultHigh, for: .vertical) 131 | } else if axis.contains(.vertical) { 132 | NSLayoutConstraint.activate([ scrollView.centerXAnchor.constraint(equalTo: host.view.centerXAnchor) ]) 133 | scrollView.setContentHuggingPriority(.defaultHigh, for: .horizontal) 134 | } else { 135 | assertionFailure("No axis for BBScrollView") 136 | } 137 | return scrollView 138 | } 139 | 140 | public func updateUIView(_ scrollView: UIScrollView, context: Context) { 141 | if let contentOffset = contentOffsetToScrollAnimated { 142 | scrollView.setContentOffset(contentOffset, animated: true) 143 | DispatchQueue.main.async { 144 | self.contentOffsetToScrollAnimated = nil 145 | } 146 | } else if contentOffset != .bb_invalidContentOffset { 147 | scrollView.contentOffset = contentOffset 148 | } 149 | 150 | updateScrollView(scrollView) 151 | 152 | let host = context.coordinator.host! 153 | host.rootView = content() 154 | host.view.setNeedsUpdateConstraints() 155 | scrollView.layoutIfNeeded() 156 | scrollView.contentSize = host.view.frame.size 157 | } 158 | 159 | public func makeCoordinator() -> BBScrollView.Coordinator { 160 | Coordinator(self) 161 | } 162 | 163 | public class Coordinator: NSObject, UIScrollViewDelegate { 164 | let parent: BBScrollView 165 | var host: UIHostingController! 166 | 167 | init(_ view: BBScrollView) { parent = view } 168 | 169 | // MARK: UIScrollViewDelegate 170 | 171 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 172 | DispatchQueue.main.async { [weak self] in 173 | guard let self = self else { return } 174 | self.parent.contentOffset = scrollView.contentOffset 175 | } 176 | } 177 | } 178 | } 179 | 180 | private class _BBScrollView: UIScrollView { 181 | let axis: Axis.Set 182 | 183 | init(_ axis: Axis.Set) { 184 | self.axis = axis 185 | super.init(frame: .zero) 186 | } 187 | 188 | required init?(coder: NSCoder) { 189 | fatalError("init(coder:) has not been implemented") 190 | } 191 | 192 | override var intrinsicContentSize: CGSize { 193 | if axis.contains(.horizontal) && !axis.contains(.vertical) { 194 | return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) 195 | } 196 | if axis.contains(.vertical) && !axis.contains(.horizontal) { 197 | return CGSize(width: contentSize.width, height: UIView.noIntrinsicMetric) 198 | } 199 | return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit/BBSwiftUIKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // BBSwiftUIKit.h 3 | // BBSwiftUIKit 4 | // 5 | // Created by Kaibo Lu on 5/12/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BBSwiftUIKit. 12 | FOUNDATION_EXPORT double BBSwiftUIKitVersionNumber; 13 | 14 | //! Project version string for BBSwiftUIKit. 15 | FOUNDATION_EXPORT const unsigned char BBSwiftUIKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit/BBTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBTableView.swift 3 | // BBSwiftUIKit 4 | // 5 | // Created by Kaibo Lu on 5/14/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public extension CGFloat { 12 | static let bb_invalidBottomSpaceForLoadingMore = CGFloat.greatestFiniteMagnitude 13 | } 14 | 15 | public extension BBTableView { 16 | func bb_reloadData(_ reloadData: Binding) -> Self { 17 | var view = self 18 | view._reloadData = reloadData 19 | return view 20 | } 21 | 22 | func bb_reloadRows(_ rows: Binding<[Int]>, animation: UITableView.RowAnimation) -> Self { 23 | var view = self 24 | view._reloadRows = rows 25 | view.reloadRowsAnimation = animation 26 | return view 27 | } 28 | 29 | func bb_scrollToRow(_ row: Binding, position: UITableView.ScrollPosition, animated: Bool) -> Self { 30 | var view = self 31 | view._scrollToRow = row 32 | view.scrollToRowPosition = position 33 | view.scrollToRowAnimated = animated 34 | return view 35 | } 36 | 37 | func bb_contentOffset(_ contentOffset: Binding) -> Self { 38 | var view = self 39 | view._contentOffset = contentOffset 40 | return view 41 | } 42 | 43 | func bb_contentOffsetToScrollAnimated(_ contentOffsetToScrollAnimated: Binding) -> Self { 44 | var view = self 45 | view._contentOffsetToScrollAnimated = contentOffsetToScrollAnimated 46 | return view 47 | } 48 | 49 | func bb_setupRefreshControl(_ setupRefreshControl: @escaping (UIRefreshControl) -> Void) -> Self { 50 | var view = self 51 | view.setupRefreshControl = setupRefreshControl 52 | return view 53 | } 54 | 55 | func bb_pullDownToRefresh(isRefreshing: Binding, refresh: @escaping () -> Void) -> Self { 56 | var view = self 57 | view._isRefreshing = isRefreshing 58 | view.pullDownToRefresh = refresh 59 | return view 60 | } 61 | 62 | func bb_pullUpToLoadMore(bottomSpace: CGFloat, loadMore: @escaping () -> Void) -> Self { 63 | var view = self 64 | view.bottomSpaceForLoadingMore = bottomSpace 65 | view.pullUpToLoadMore = loadMore 66 | return view 67 | } 68 | 69 | func bb_setupTableView(_ setupTableView: @escaping (UITableView) -> Void) -> Self { 70 | var view = self 71 | view.setupTableView = setupTableView 72 | return view 73 | } 74 | 75 | func bb_updateTableView(_ updateTableView: @escaping (UITableView) -> Void) -> Self { 76 | var view = self 77 | view.updateTableView = updateTableView 78 | return view 79 | } 80 | } 81 | 82 | public struct BBTableView: UIViewControllerRepresentable, BBUIScrollViewRepresentable where Data : RandomAccessCollection, Content : View, Data.Element : Equatable { 83 | let data: Data 84 | let content: (Data.Element) -> Content 85 | 86 | @Binding public var reloadData: Bool 87 | 88 | @Binding public var reloadRows: [Int] 89 | public var reloadRowsAnimation: UITableView.RowAnimation = .automatic 90 | 91 | @Binding public var scrollToRow: Int? 92 | public var scrollToRowPosition: UITableView.ScrollPosition = .none 93 | public var scrollToRowAnimated: Bool = true 94 | 95 | @Binding public var contentOffset: CGPoint 96 | @Binding public var contentOffsetToScrollAnimated: CGPoint? 97 | public var isPagingEnabled: Bool = false 98 | public var bounces: Bool = true 99 | public var alwaysBounceVertical: Bool = false 100 | public var alwaysBounceHorizontal: Bool = false 101 | public var showsVerticalScrollIndicator: Bool = true 102 | public var showsHorizontalScrollIndicator: Bool = true 103 | 104 | @Binding public var isRefreshing: Bool 105 | public var setupRefreshControl: ((UIRefreshControl) -> Void)? 106 | public var pullDownToRefresh: (() -> Void)? 107 | public var bottomSpaceForLoadingMore: CGFloat = .bb_invalidBottomSpaceForLoadingMore 108 | public var pullUpToLoadMore: (() -> Void)? 109 | 110 | public var setupTableView: ((UITableView) -> Void)? 111 | public var updateTableView: ((UITableView) -> Void)? 112 | 113 | public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content) { 114 | self.data = data 115 | self.content = content 116 | self._reloadData = .constant(false) 117 | self._reloadRows = .constant([]) 118 | self._scrollToRow = .constant(nil) 119 | self._contentOffset = .constant(.bb_invalidContentOffset) 120 | self._contentOffsetToScrollAnimated = .constant(nil) 121 | self._isRefreshing = .constant(false) 122 | } 123 | 124 | public func makeUIViewController(context: Context) -> UIViewController { 125 | _BBTableViewController(self) 126 | } 127 | 128 | public func updateUIViewController(_ viewController: UIViewController, context: Context) { 129 | let vc = viewController as! _BBTableViewController 130 | updateScrollView(vc.tableView) 131 | vc.update(self) 132 | } 133 | } 134 | 135 | private class _BBTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate where Data: RandomAccessCollection, Content: View, Data.Element: Equatable { 136 | var representable: BBTableView 137 | var tableView: UITableView! 138 | 139 | var data: Data { representable.data } 140 | var content: (Data.Element) -> Content { representable.content } 141 | 142 | init(_ view: BBTableView) { 143 | representable = view 144 | super.init(nibName: nil, bundle: nil) 145 | } 146 | 147 | required init?(coder: NSCoder) { 148 | fatalError("init(coder:) has not been implemented") 149 | } 150 | 151 | override func viewDidLoad() { 152 | super.viewDidLoad() 153 | 154 | tableView = UITableView() 155 | tableView.translatesAutoresizingMaskIntoConstraints = false 156 | tableView.register(_BBTableViewHostCell.self, forCellReuseIdentifier: "cell") 157 | tableView.separatorStyle = .none 158 | tableView.dataSource = self 159 | tableView.delegate = self 160 | view.addSubview(tableView) 161 | 162 | if representable.setupRefreshControl != nil || representable.pullDownToRefresh != nil { 163 | let refreshControl = UIRefreshControl() 164 | representable.setupRefreshControl?(refreshControl) 165 | refreshControl.addTarget(self, action: #selector(pullDownToRefresh), for: .valueChanged) 166 | tableView.refreshControl = refreshControl 167 | } 168 | 169 | NSLayoutConstraint.activate([ 170 | view.leftAnchor.constraint(equalTo: tableView.leftAnchor), 171 | view.rightAnchor.constraint(equalTo: tableView.rightAnchor), 172 | view.topAnchor.constraint(equalTo: tableView.topAnchor), 173 | view.bottomAnchor.constraint(equalTo: tableView.bottomAnchor) 174 | ]) 175 | 176 | representable.setupTableView?(tableView) 177 | } 178 | 179 | func update(_ newRepresentable: BBTableView) { 180 | if newRepresentable.reloadData { 181 | representable = newRepresentable 182 | tableView.reloadData() 183 | 184 | DispatchQueue.main.async { [weak self] in 185 | guard let self = self else { return } 186 | self.representable.reloadData = false 187 | self.representable.reloadRows.removeAll() 188 | } 189 | } else { 190 | var removals: [IndexPath] = [] 191 | var insertions: [IndexPath] = [] 192 | let diff = newRepresentable.data.difference(from: data) 193 | for step in diff { 194 | switch step { 195 | case let .remove(i, _, _): removals.append(IndexPath(row: i, section: 0)) 196 | case let .insert(i, _, _): insertions.append(IndexPath(row: i, section: 0)) 197 | } 198 | } 199 | 200 | representable = newRepresentable 201 | 202 | if !removals.isEmpty || !insertions.isEmpty { 203 | tableView.performBatchUpdates({ 204 | tableView.deleteRows(at: removals, with: .automatic) 205 | tableView.insertRows(at: insertions, with: .automatic) 206 | }, completion: nil) 207 | } 208 | 209 | if !representable.reloadRows.isEmpty { 210 | tableView.reloadRows(at: representable.reloadRows.map { IndexPath(row: $0, section: 0) }, with: representable.reloadRowsAnimation) 211 | 212 | DispatchQueue.main.async { [weak self] in 213 | guard let self = self else { return } 214 | self.representable.reloadRows.removeAll() 215 | } 216 | } 217 | } 218 | 219 | if let refreshControl = tableView.refreshControl, 220 | representable.isRefreshing != refreshControl.isRefreshing { 221 | if representable.isRefreshing { 222 | refreshControl.beginRefreshing() 223 | } else { 224 | refreshControl.endRefreshing() 225 | } 226 | } 227 | 228 | representable.updateTableView?(tableView) 229 | 230 | if let row = representable.scrollToRow { 231 | tableView.scrollToRow(at: IndexPath(row: row, section: 0), at: representable.scrollToRowPosition, animated: representable.scrollToRowAnimated) 232 | DispatchQueue.main.async { [weak self] in 233 | guard let self = self else { return } 234 | self.representable.scrollToRow = nil 235 | } 236 | } else if let contentOffset = representable.contentOffsetToScrollAnimated { 237 | tableView.setContentOffset(contentOffset, animated: true) 238 | DispatchQueue.main.async { [weak self] in 239 | guard let self = self else { return } 240 | self.representable.contentOffsetToScrollAnimated = nil 241 | } 242 | } else if representable.contentOffset != .bb_invalidContentOffset { 243 | tableView.contentOffset = representable.contentOffset 244 | } 245 | } 246 | 247 | @objc private func pullDownToRefresh() { 248 | representable.isRefreshing = true 249 | representable.pullDownToRefresh?() 250 | } 251 | 252 | // MARK: UITableViewDataSource 253 | 254 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { data.count } 255 | 256 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 257 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! _BBTableViewHostCell 258 | let index = data.index(data.startIndex, offsetBy: indexPath.row) 259 | let view = content(data[index]) 260 | cell.update(view, parent: self) 261 | return cell 262 | } 263 | 264 | // MARK: UITableViewDelegate 265 | 266 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 267 | DispatchQueue.main.async { [weak self] in 268 | guard let self = self else { return } 269 | self.representable.contentOffset = scrollView.contentOffset 270 | 271 | if self.representable.bottomSpaceForLoadingMore != .bb_invalidBottomSpaceForLoadingMore, 272 | let loadMore = self.representable.pullUpToLoadMore { 273 | let space = scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.height 274 | if space <= self.representable.bottomSpaceForLoadingMore { 275 | loadMore() 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | private class _BBTableViewHostCell: UITableViewCell { 283 | var host: UIHostingController! 284 | 285 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 286 | super.init(style: style, reuseIdentifier: reuseIdentifier) 287 | 288 | selectionStyle = .none 289 | } 290 | 291 | required init?(coder: NSCoder) { 292 | fatalError("init(coder:) has not been implemented") 293 | } 294 | 295 | func update(_ content: Content, parent: UIViewController) { 296 | if host == nil { 297 | host = UIHostingController(rootView: content) 298 | parent.addChild(host) 299 | 300 | host.view.translatesAutoresizingMaskIntoConstraints = false 301 | contentView.addSubview(host.view) 302 | 303 | NSLayoutConstraint.activate([ 304 | contentView.leftAnchor.constraint(equalTo: host.view.leftAnchor), 305 | contentView.rightAnchor.constraint(equalTo: host.view.rightAnchor), 306 | contentView.topAnchor.constraint(equalTo: host.view.topAnchor), 307 | contentView.bottomAnchor.constraint(equalTo: host.view.bottomAnchor) 308 | ]) 309 | 310 | host.didMove(toParent: parent) 311 | } else { 312 | host.rootView = content 313 | } 314 | host.view.setNeedsUpdateConstraints() 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /BBSwiftUIKit/BBSwiftUIKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4A140B16246B317600863A16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B15246B317600863A16 /* AppDelegate.swift */; }; 11 | 4A140B18246B317600863A16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B17246B317600863A16 /* SceneDelegate.swift */; }; 12 | 4A140B1C246B317700863A16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A140B1B246B317700863A16 /* Assets.xcassets */; }; 13 | 4A140B1F246B317700863A16 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A140B1E246B317700863A16 /* Preview Assets.xcassets */; }; 14 | 4A140B22246B317700863A16 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4A140B20246B317700863A16 /* LaunchScreen.storyboard */; }; 15 | 4A140B2F246B318A00863A16 /* ScrollViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B29246B318A00863A16 /* ScrollViewExample.swift */; }; 16 | 4A140B30246B318A00863A16 /* ScrollViewExample2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B2A246B318A00863A16 /* ScrollViewExample2.swift */; }; 17 | 4A140B31246B318A00863A16 /* ScrollViewExample3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B2B246B318A00863A16 /* ScrollViewExample3.swift */; }; 18 | 4A140B32246B318A00863A16 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B2C246B318A00863A16 /* ContentView.swift */; }; 19 | 4A140B33246B318A00863A16 /* ScrollViewExample4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B2D246B318A00863A16 /* ScrollViewExample4.swift */; }; 20 | 4A140B34246B318A00863A16 /* CycleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B2E246B318A00863A16 /* CycleView.swift */; }; 21 | 4A140B37246B31F800863A16 /* BBSwiftUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A140B36246B31F800863A16 /* BBSwiftUIKit.framework */; }; 22 | 4A140B38246B31F800863A16 /* BBSwiftUIKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4A140B36246B31F800863A16 /* BBSwiftUIKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 23 | 4A140B3D246B745C00863A16 /* CycleViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B3C246B745C00863A16 /* CycleViewExample.swift */; }; 24 | 4A140B3F246B7CFD00863A16 /* PageControlExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A140B3E246B7CFD00863A16 /* PageControlExample.swift */; }; 25 | 4A9245EB246E11EC00BA48AF /* TableViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9245EA246E11EC00BA48AF /* TableViewExample.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXCopyFilesBuildPhase section */ 29 | 4A140B39246B31F800863A16 /* Embed Frameworks */ = { 30 | isa = PBXCopyFilesBuildPhase; 31 | buildActionMask = 2147483647; 32 | dstPath = ""; 33 | dstSubfolderSpec = 10; 34 | files = ( 35 | 4A140B38246B31F800863A16 /* BBSwiftUIKit.framework in Embed Frameworks */, 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 4A140B12246B317600863A16 /* BBSwiftUIKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BBSwiftUIKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 4A140B15246B317600863A16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 4A140B17246B317600863A16 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 46 | 4A140B1B246B317700863A16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 4A140B1E246B317700863A16 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 48 | 4A140B21246B317700863A16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 4A140B23246B317700863A16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 4A140B29246B318A00863A16 /* ScrollViewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewExample.swift; sourceTree = ""; }; 51 | 4A140B2A246B318A00863A16 /* ScrollViewExample2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewExample2.swift; sourceTree = ""; }; 52 | 4A140B2B246B318A00863A16 /* ScrollViewExample3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewExample3.swift; sourceTree = ""; }; 53 | 4A140B2C246B318A00863A16 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 54 | 4A140B2D246B318A00863A16 /* ScrollViewExample4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewExample4.swift; sourceTree = ""; }; 55 | 4A140B2E246B318A00863A16 /* CycleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CycleView.swift; sourceTree = ""; }; 56 | 4A140B36246B31F800863A16 /* BBSwiftUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BBSwiftUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 4A140B3C246B745C00863A16 /* CycleViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CycleViewExample.swift; sourceTree = ""; }; 58 | 4A140B3E246B7CFD00863A16 /* PageControlExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlExample.swift; sourceTree = ""; }; 59 | 4A9245EA246E11EC00BA48AF /* TableViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewExample.swift; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 4A140B0F246B317600863A16 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 4A140B37246B31F800863A16 /* BBSwiftUIKit.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 4A140B09246B317600863A16 = { 75 | isa = PBXGroup; 76 | children = ( 77 | 4A140B14246B317600863A16 /* BBSwiftUIKitDemo */, 78 | 4A140B13246B317600863A16 /* Products */, 79 | 4A140B35246B31F800863A16 /* Frameworks */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 4A140B13246B317600863A16 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 4A140B12246B317600863A16 /* BBSwiftUIKitDemo.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 4A140B14246B317600863A16 /* BBSwiftUIKitDemo */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 4A140B15246B317600863A16 /* AppDelegate.swift */, 95 | 4A140B17246B317600863A16 /* SceneDelegate.swift */, 96 | 4A140B2C246B318A00863A16 /* ContentView.swift */, 97 | 4A140B29246B318A00863A16 /* ScrollViewExample.swift */, 98 | 4A140B2A246B318A00863A16 /* ScrollViewExample2.swift */, 99 | 4A140B2B246B318A00863A16 /* ScrollViewExample3.swift */, 100 | 4A140B2D246B318A00863A16 /* ScrollViewExample4.swift */, 101 | 4A140B3E246B7CFD00863A16 /* PageControlExample.swift */, 102 | 4A140B3C246B745C00863A16 /* CycleViewExample.swift */, 103 | 4A140B2E246B318A00863A16 /* CycleView.swift */, 104 | 4A9245EA246E11EC00BA48AF /* TableViewExample.swift */, 105 | 4A140B1B246B317700863A16 /* Assets.xcassets */, 106 | 4A140B20246B317700863A16 /* LaunchScreen.storyboard */, 107 | 4A140B23246B317700863A16 /* Info.plist */, 108 | 4A140B1D246B317700863A16 /* Preview Content */, 109 | ); 110 | path = BBSwiftUIKitDemo; 111 | sourceTree = ""; 112 | }; 113 | 4A140B1D246B317700863A16 /* Preview Content */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 4A140B1E246B317700863A16 /* Preview Assets.xcassets */, 117 | ); 118 | path = "Preview Content"; 119 | sourceTree = ""; 120 | }; 121 | 4A140B35246B31F800863A16 /* Frameworks */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 4A140B36246B31F800863A16 /* BBSwiftUIKit.framework */, 125 | ); 126 | name = Frameworks; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | 4A140B11246B317600863A16 /* BBSwiftUIKitDemo */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 4A140B26246B317700863A16 /* Build configuration list for PBXNativeTarget "BBSwiftUIKitDemo" */; 135 | buildPhases = ( 136 | 4A140B0E246B317600863A16 /* Sources */, 137 | 4A140B0F246B317600863A16 /* Frameworks */, 138 | 4A140B10246B317600863A16 /* Resources */, 139 | 4A140B39246B31F800863A16 /* Embed Frameworks */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | ); 145 | name = BBSwiftUIKitDemo; 146 | productName = BBSwiftUIKitDemo; 147 | productReference = 4A140B12246B317600863A16 /* BBSwiftUIKitDemo.app */; 148 | productType = "com.apple.product-type.application"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | 4A140B0A246B317600863A16 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | LastSwiftUpdateCheck = 1130; 157 | LastUpgradeCheck = 1130; 158 | ORGANIZATIONNAME = "Kaibo Lu"; 159 | TargetAttributes = { 160 | 4A140B11246B317600863A16 = { 161 | CreatedOnToolsVersion = 11.3; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = 4A140B0D246B317600863A16 /* Build configuration list for PBXProject "BBSwiftUIKitDemo" */; 166 | compatibilityVersion = "Xcode 9.3"; 167 | developmentRegion = en; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | Base, 172 | ); 173 | mainGroup = 4A140B09246B317600863A16; 174 | productRefGroup = 4A140B13246B317600863A16 /* Products */; 175 | projectDirPath = ""; 176 | projectRoot = ""; 177 | targets = ( 178 | 4A140B11246B317600863A16 /* BBSwiftUIKitDemo */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | 4A140B10246B317600863A16 /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | 4A140B22246B317700863A16 /* LaunchScreen.storyboard in Resources */, 189 | 4A140B1F246B317700863A16 /* Preview Assets.xcassets in Resources */, 190 | 4A140B1C246B317700863A16 /* Assets.xcassets in Resources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXResourcesBuildPhase section */ 195 | 196 | /* Begin PBXSourcesBuildPhase section */ 197 | 4A140B0E246B317600863A16 /* Sources */ = { 198 | isa = PBXSourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | 4A140B16246B317600863A16 /* AppDelegate.swift in Sources */, 202 | 4A140B18246B317600863A16 /* SceneDelegate.swift in Sources */, 203 | 4A140B32246B318A00863A16 /* ContentView.swift in Sources */, 204 | 4A9245EB246E11EC00BA48AF /* TableViewExample.swift in Sources */, 205 | 4A140B31246B318A00863A16 /* ScrollViewExample3.swift in Sources */, 206 | 4A140B30246B318A00863A16 /* ScrollViewExample2.swift in Sources */, 207 | 4A140B34246B318A00863A16 /* CycleView.swift in Sources */, 208 | 4A140B2F246B318A00863A16 /* ScrollViewExample.swift in Sources */, 209 | 4A140B3D246B745C00863A16 /* CycleViewExample.swift in Sources */, 210 | 4A140B33246B318A00863A16 /* ScrollViewExample4.swift in Sources */, 211 | 4A140B3F246B7CFD00863A16 /* PageControlExample.swift in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 4A140B20246B317700863A16 /* LaunchScreen.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 4A140B21246B317700863A16 /* Base */, 222 | ); 223 | name = LaunchScreen.storyboard; 224 | sourceTree = ""; 225 | }; 226 | /* End PBXVariantGroup section */ 227 | 228 | /* Begin XCBuildConfiguration section */ 229 | 4A140B24246B317700863A16 /* Debug */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_ANALYZER_NONNULL = YES; 234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_ENABLE_OBJC_WEAK = YES; 240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 241 | CLANG_WARN_BOOL_CONVERSION = YES; 242 | CLANG_WARN_COMMA = YES; 243 | CLANG_WARN_CONSTANT_CONVERSION = YES; 244 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 247 | CLANG_WARN_EMPTY_BODY = YES; 248 | CLANG_WARN_ENUM_CONVERSION = YES; 249 | CLANG_WARN_INFINITE_RECURSION = YES; 250 | CLANG_WARN_INT_CONVERSION = YES; 251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 253 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 255 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 256 | CLANG_WARN_STRICT_PROTOTYPES = YES; 257 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 258 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | COPY_PHASE_STRIP = NO; 262 | DEBUG_INFORMATION_FORMAT = dwarf; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | ENABLE_TESTABILITY = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu11; 266 | GCC_DYNAMIC_NO_PIC = NO; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_OPTIMIZATION_LEVEL = 0; 269 | GCC_PREPROCESSOR_DEFINITIONS = ( 270 | "DEBUG=1", 271 | "$(inherited)", 272 | ); 273 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 274 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 275 | GCC_WARN_UNDECLARED_SELECTOR = YES; 276 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 277 | GCC_WARN_UNUSED_FUNCTION = YES; 278 | GCC_WARN_UNUSED_VARIABLE = YES; 279 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 280 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 281 | MTL_FAST_MATH = YES; 282 | ONLY_ACTIVE_ARCH = YES; 283 | SDKROOT = iphoneos; 284 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 285 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 286 | }; 287 | name = Debug; 288 | }; 289 | 4A140B25246B317700863A16 /* Release */ = { 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++14"; 296 | CLANG_CXX_LIBRARY = "libc++"; 297 | CLANG_ENABLE_MODULES = YES; 298 | CLANG_ENABLE_OBJC_ARC = YES; 299 | CLANG_ENABLE_OBJC_WEAK = YES; 300 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 301 | CLANG_WARN_BOOL_CONVERSION = YES; 302 | CLANG_WARN_COMMA = YES; 303 | CLANG_WARN_CONSTANT_CONVERSION = YES; 304 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 305 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 306 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 313 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 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-with-dsym"; 323 | ENABLE_NS_ASSERTIONS = NO; 324 | ENABLE_STRICT_OBJC_MSGSEND = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu11; 326 | GCC_NO_COMMON_BLOCKS = YES; 327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 329 | GCC_WARN_UNDECLARED_SELECTOR = YES; 330 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 331 | GCC_WARN_UNUSED_FUNCTION = YES; 332 | GCC_WARN_UNUSED_VARIABLE = YES; 333 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 334 | MTL_ENABLE_DEBUG_INFO = NO; 335 | MTL_FAST_MATH = YES; 336 | SDKROOT = iphoneos; 337 | SWIFT_COMPILATION_MODE = wholemodule; 338 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 339 | VALIDATE_PRODUCT = YES; 340 | }; 341 | name = Release; 342 | }; 343 | 4A140B27246B317700863A16 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 347 | CODE_SIGN_STYLE = Automatic; 348 | DEVELOPMENT_ASSET_PATHS = "\"BBSwiftUIKitDemo/Preview Content\""; 349 | DEVELOPMENT_TEAM = 9LJHCTN42A; 350 | ENABLE_PREVIEWS = YES; 351 | INFOPLIST_FILE = BBSwiftUIKitDemo/Info.plist; 352 | LD_RUNPATH_SEARCH_PATHS = ( 353 | "$(inherited)", 354 | "@executable_path/Frameworks", 355 | ); 356 | PRODUCT_BUNDLE_IDENTIFIER = Kaibo.BBSwiftUIKitDemo; 357 | PRODUCT_NAME = "$(TARGET_NAME)"; 358 | SWIFT_VERSION = 5.0; 359 | TARGETED_DEVICE_FAMILY = "1,2"; 360 | }; 361 | name = Debug; 362 | }; 363 | 4A140B28246B317700863A16 /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 367 | CODE_SIGN_STYLE = Automatic; 368 | DEVELOPMENT_ASSET_PATHS = "\"BBSwiftUIKitDemo/Preview Content\""; 369 | DEVELOPMENT_TEAM = 9LJHCTN42A; 370 | ENABLE_PREVIEWS = YES; 371 | INFOPLIST_FILE = BBSwiftUIKitDemo/Info.plist; 372 | LD_RUNPATH_SEARCH_PATHS = ( 373 | "$(inherited)", 374 | "@executable_path/Frameworks", 375 | ); 376 | PRODUCT_BUNDLE_IDENTIFIER = Kaibo.BBSwiftUIKitDemo; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Release; 382 | }; 383 | /* End XCBuildConfiguration section */ 384 | 385 | /* Begin XCConfigurationList section */ 386 | 4A140B0D246B317600863A16 /* Build configuration list for PBXProject "BBSwiftUIKitDemo" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | 4A140B24246B317700863A16 /* Debug */, 390 | 4A140B25246B317700863A16 /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Release; 394 | }; 395 | 4A140B26246B317700863A16 /* Build configuration list for PBXNativeTarget "BBSwiftUIKitDemo" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | 4A140B27246B317700863A16 /* Debug */, 399 | 4A140B28246B317700863A16 /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | defaultConfigurationName = Release; 403 | }; 404 | /* End XCConfigurationList section */ 405 | }; 406 | rootObject = 4A140B0A246B317600863A16 /* Project object */; 407 | } 408 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/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 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MenuItem { 12 | let title: String 13 | let view: Content 14 | } 15 | 16 | extension View { 17 | var toAnyView: AnyView { AnyView(self) } 18 | } 19 | 20 | struct ContentView: View { 21 | let items: [MenuItem] = [ 22 | MenuItem(title: "Scroll view", view: ScrollViewExample().toAnyView), 23 | MenuItem(title: "Scroll view 2", view: ScrollViewExample2().toAnyView), 24 | MenuItem(title: "Scroll view 3", view: ScrollViewExample3().toAnyView), 25 | MenuItem(title: "Scroll view 4", view: ScrollViewExample4().toAnyView), 26 | MenuItem(title: "Page control", view: PageControlExample().toAnyView), 27 | MenuItem(title: "Cycle view manual", view: CycleViewExample(autoDisplay: false).toAnyView), 28 | MenuItem(title: "Cycle view auto", view: CycleViewExample(autoDisplay: true).toAnyView), 29 | MenuItem(title: "Table view", view: TableViewExample().toAnyView) 30 | ] 31 | 32 | var body: some View { 33 | NavigationView { 34 | List(items, id: \.title) { item in 35 | NavigationLink(destination: item.view) { 36 | Text(item.title) 37 | } 38 | } 39 | .navigationBarTitle("", displayMode: .inline) 40 | } 41 | } 42 | } 43 | 44 | struct ContentView_Previews: PreviewProvider { 45 | static var previews: some View { 46 | ContentView() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/CycleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CycleView.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 5/4/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct CycleView: View { 13 | @ObservedObject private var model: CycleViewModel 14 | 15 | init(names: [String], pageSize: CGSize, autoDisplay: Bool) { 16 | model = CycleViewModel(names: names, pageSize: pageSize, autoDisplay: autoDisplay) 17 | } 18 | 19 | var body: some View { 20 | VStack { 21 | BBScrollView(.horizontal, contentOffset: $model.contentOffset) { 22 | HStack(spacing: 0) { 23 | ForEach(self.model.nameIndexes, id: \.self) { i in 24 | Image(systemName: self.model.names[i]) 25 | .resizable() 26 | .scaledToFit() 27 | .frame(width: self.model.pageSize.width, height: self.model.pageSize.height) 28 | } 29 | } 30 | } 31 | .bb_showsHorizontalScrollIndicator(false) 32 | .bb_isPagingEnabled(true) 33 | .bb_contentOffsetToScrollAnimated($model.contentOffsetToScrollAnimated) 34 | .onAppear { 35 | self.model.appear = true 36 | self.model.start() 37 | } 38 | .onDisappear { 39 | self.model.appear = false 40 | self.model.stop() 41 | } 42 | 43 | BBPageControl(currentPage: $model.index, numberOfPages: model.names.count) 44 | .background(Color.black) 45 | } 46 | } 47 | } 48 | 49 | struct CycleView_Previews: PreviewProvider { 50 | static var previews: some View { 51 | CycleView(names: ["sun.max", "moon", "star", "cloud"], pageSize: CGSize(width: UIScreen.main.bounds.width, height: 500), autoDisplay: false) 52 | } 53 | } 54 | 55 | fileprivate class CycleViewModel: ObservableObject { 56 | let names: [String] 57 | let pageSize: CGSize 58 | let autoDisplay: Bool 59 | 60 | var appear: Bool = false 61 | var timer: Timer? 62 | 63 | @Published var index = 0 64 | 65 | @Published var contentOffset: CGPoint { 66 | didSet { 67 | if contentOffset.x <= 0 { 68 | index = nameIndexes[0] 69 | contentOffset.x += pageSize.width 70 | } else if contentOffset.x >= pageSize.width * 2 { 71 | index = nameIndexes[2] 72 | contentOffset.x -= pageSize.width 73 | } 74 | } 75 | } 76 | 77 | @Published var contentOffsetToScrollAnimated: CGPoint? = nil 78 | 79 | var nameIndexes: [Int] { 80 | if index == 0 { return [names.count - 1, 0, 1] } 81 | let nextIndex = index == names.count - 1 ? 0 : index + 1 82 | return [index - 1, index, nextIndex] 83 | } 84 | 85 | init(names: [String], pageSize: CGSize, autoDisplay: Bool) { 86 | self.names = names 87 | self.pageSize = pageSize 88 | self.autoDisplay = autoDisplay 89 | self.contentOffset = CGPoint(x: pageSize.width, y: 0) 90 | } 91 | 92 | func start() { 93 | if !autoDisplay || timer != nil { return } 94 | timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in 95 | guard let self = self, self.appear else { return } 96 | self.contentOffsetToScrollAnimated = CGPoint(x: self.pageSize.width * 2, y: 0) 97 | } 98 | } 99 | 100 | func stop() { 101 | timer?.invalidate() 102 | timer = nil 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/CycleViewExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CycleViewExample.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 5/12/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct CycleViewExample: View { 13 | let autoDisplay: Bool 14 | 15 | @State var index: Int = 0 16 | 17 | private let names: [String] = ["sun.max", "moon", "star", "cloud"] 18 | 19 | var body: some View { 20 | CycleView(names: names, pageSize: CGSize(width: UIScreen.main.bounds.width, height: 500), autoDisplay: autoDisplay) 21 | } 22 | } 23 | 24 | struct CycleViewExample_Previews: PreviewProvider { 25 | static var previews: some View { 26 | CycleViewExample(autoDisplay: false) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/PageControlExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageControlExample.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 5/12/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct PageControlExample: View { 13 | @State var currentPage: Int = 0 14 | @State var numberOfPages: Int = 5 15 | @State var hidesForSinglePage: Bool = false 16 | @State var pageIndicatorTintColor: UIColor? 17 | @State var currentPageIndicatorTintColor: UIColor? 18 | 19 | var body: some View { 20 | VStack { 21 | BBPageControl(currentPage: $currentPage, numberOfPages: numberOfPages) 22 | .bb_hidesForSinglePage(hidesForSinglePage) 23 | .bb_pageIndicatorTintColor(pageIndicatorTintColor) 24 | .bb_currentPageIndicatorTintColor(currentPageIndicatorTintColor) 25 | .background(Color.black) 26 | 27 | Button("Current page \(currentPage)") { 28 | self.currentPage = self.numberOfPages == 0 ? 0 : (self.currentPage + 1) % self.numberOfPages 29 | } 30 | .padding() 31 | 32 | Button("Number of pages \(numberOfPages)") { 33 | self.numberOfPages = (self.numberOfPages + 1) % 10 34 | } 35 | .padding() 36 | 37 | Button("Hides for single page \(hidesForSinglePage ? "true" : "false")") { 38 | self.hidesForSinglePage.toggle() 39 | } 40 | .padding() 41 | 42 | Button("Page indicator tint color \(pageIndicatorTintColor == nil ? "nil" : "blue")") { 43 | self.pageIndicatorTintColor = self.pageIndicatorTintColor == nil ? .blue : nil 44 | } 45 | .padding() 46 | 47 | Button("Current page indicator tint color \(currentPageIndicatorTintColor == nil ? "nil" : "red")") { 48 | self.currentPageIndicatorTintColor = self.currentPageIndicatorTintColor == nil ? .red : nil 49 | } 50 | .padding() 51 | } 52 | } 53 | } 54 | 55 | struct PageControlExample_Previews: PreviewProvider { 56 | static var previews: some View { 57 | PageControlExample() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/ScrollViewExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewExample.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct ScrollViewExample: View { 13 | @State var contentOffset: CGPoint = .zero 14 | @State var bounces: Bool = true 15 | @State var isPagingEnabled: Bool = false 16 | @State var showsHorizontalScrollIndicator: Bool = true 17 | @State var contentOffsetToScrollAnimated: CGPoint? = nil 18 | @State var longContent: Bool = false 19 | 20 | var body: some View { 21 | VStack { 22 | BBScrollView(.horizontal, contentOffset: $contentOffset) { 23 | HStack(spacing: 0) { 24 | Text(self.longContent ? "AAA" : "A") 25 | .frame(width: UIScreen.main.bounds.width) 26 | .background(Color.red) 27 | Text(self.longContent ? "BBB" : "B") 28 | .frame(width: UIScreen.main.bounds.width) 29 | .background(Color.blue) 30 | if self.longContent { 31 | Text("CCC") 32 | .frame(width: UIScreen.main.bounds.width) 33 | .background(Color.green) 34 | } 35 | } 36 | } 37 | .bb_bounces(bounces) 38 | .bb_isPagingEnabled(isPagingEnabled) 39 | .bb_showsHorizontalScrollIndicator(showsHorizontalScrollIndicator) 40 | .bb_contentOffsetToScrollAnimated($contentOffsetToScrollAnimated) 41 | 42 | Slider(value: $contentOffset.x, in: 0...UIScreen.main.bounds.width * (self.longContent ? 2 : 1)) 43 | 44 | Button("Scroll to x = 100") { 45 | self.contentOffsetToScrollAnimated = CGPoint(x: 100, y: 0) 46 | } 47 | .padding() 48 | 49 | Button("Bounces \(bounces ? "true" : "false")") { 50 | self.bounces.toggle() 51 | } 52 | .padding() 53 | 54 | Button("Paging enabled \(isPagingEnabled ? "true" : "false")") { 55 | self.isPagingEnabled.toggle() 56 | } 57 | .padding() 58 | 59 | Button("Shows horizontal scroll indicator \(showsHorizontalScrollIndicator ? "true" : "false")") { 60 | self.showsHorizontalScrollIndicator.toggle() 61 | } 62 | .padding() 63 | 64 | Button("\(longContent ? 3 : 2) screen width") { 65 | self.longContent.toggle() 66 | } 67 | .padding() 68 | } 69 | } 70 | } 71 | 72 | struct ScrollViewExample_Previews: PreviewProvider { 73 | static var previews: some View { 74 | ScrollViewExample() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/ScrollViewExample2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewExample2.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct ScrollViewExample2: View { 13 | @State var contentOffset: CGPoint = .zero 14 | @State var showsVerticalScrollIndicator: Bool = true 15 | 16 | var body: some View { 17 | VStack { 18 | BBScrollView(.vertical, contentOffset: $contentOffset) { 19 | VStack { 20 | Text("1") 21 | .frame(width: 50, height: UIScreen.main.bounds.height) 22 | .background(Color.red) 23 | Text("2") 24 | .frame(width: 50, height: UIScreen.main.bounds.height) 25 | .background(Color.blue) 26 | Text("3") 27 | .frame(width: 50, height: UIScreen.main.bounds.height) 28 | .background(Color.green) 29 | } 30 | } 31 | .bb_showsVerticalScrollIndicator(showsVerticalScrollIndicator) 32 | .edgesIgnoringSafeArea(.all) 33 | 34 | Slider(value: self.$contentOffset.y, in: 0...UIScreen.main.bounds.height * 2) 35 | 36 | Button("Shows vertical scroll indicator \(self.showsVerticalScrollIndicator ? "true" : "false")") { 37 | self.showsVerticalScrollIndicator.toggle() 38 | } 39 | .padding() 40 | } 41 | } 42 | } 43 | 44 | struct ScrollViewExample2_Previews: PreviewProvider { 45 | static var previews: some View { 46 | ScrollViewExample2() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/ScrollViewExample3.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewExample3.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct ScrollViewExample3: View { 13 | @State var contentOffset: CGPoint = .zero 14 | 15 | var body: some View { 16 | BBScrollView([.horizontal, .vertical], contentOffset: $contentOffset) { 17 | VStack { 18 | Text("1") 19 | .frame(width: UIScreen.main.bounds.width * 2, height: UIScreen.main.bounds.height) 20 | .background(Color.red) 21 | Text("2") 22 | .frame(width: UIScreen.main.bounds.width * 2, height: UIScreen.main.bounds.height) 23 | .background(Color.blue) 24 | Text("3") 25 | .frame(width: UIScreen.main.bounds.width * 2, height: UIScreen.main.bounds.height) 26 | .background(Color.green) 27 | } 28 | } 29 | } 30 | } 31 | 32 | struct ScrollViewExample3_Previews: PreviewProvider { 33 | static var previews: some View { 34 | ScrollViewExample3() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/ScrollViewExample4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewExample4.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 4/19/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct ScrollViewExample4: View { 13 | @State var contentOffset: CGPoint = .zero 14 | 15 | var body: some View { 16 | BBScrollView([.horizontal, .vertical]) { 17 | Text("Hello, World!") 18 | Image(systemName: "heart") 19 | } 20 | } 21 | } 22 | 23 | struct ScrollViewExample4_Previews: PreviewProvider { 24 | static var previews: some View { 25 | ScrollViewExample4() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BBSwiftUIKitDemo/BBSwiftUIKitDemo/TableViewExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewExample.swift 3 | // BBSwiftUIKitDemo 4 | // 5 | // Created by Kaibo Lu on 5/14/20. 6 | // Copyright © 2020 Kaibo Lu. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BBSwiftUIKit 11 | 12 | struct TableViewExample: View { 13 | 14 | class Model: ObservableObject { 15 | @Published var list: Range = 0..<100 16 | @Published var updateHeight: Bool = false 17 | @Published var reloadData: Bool = false 18 | @Published var reloadRows: [Int] = [] 19 | @Published var scrollToRow: Int? = nil 20 | @Published var contentOffset: CGPoint = .zero 21 | @Published var contentOffsetToScrollAnimated: CGPoint? = nil 22 | @Published var isRefreshing: Bool = false 23 | @Published var isLoadingMore: Bool = false 24 | 25 | func reloadListData() { 26 | self.list = 0..<100 27 | self.updateHeight.toggle() 28 | self.reloadData = true 29 | } 30 | 31 | func reloadListRows() { 32 | list = 0..<100 33 | updateHeight.toggle() 34 | reloadRows = (0..<10).map { $0 } 35 | } 36 | } 37 | 38 | @ObservedObject var model = Model() 39 | 40 | var body: some View { 41 | VStack { 42 | BBTableView(model.list) { i in 43 | if i % 2 == 0 { 44 | Text("\(i)\(self.model.updateHeight ? "" : "A")") 45 | .frame(height: self.model.updateHeight ? 50 : 100) 46 | .padding() 47 | .background(Color.blue) 48 | } else { 49 | Image(systemName: "heart") 50 | .resizable() 51 | .scaledToFit() 52 | .frame(height: self.model.updateHeight ? 20 : 40) 53 | .padding() 54 | .background(Color.orange) 55 | } 56 | } 57 | .bb_reloadData($model.reloadData) 58 | .bb_reloadRows($model.reloadRows, animation: .automatic) 59 | .bb_scrollToRow($model.scrollToRow, position: .none, animated: true) 60 | .bb_contentOffset($model.contentOffset) 61 | .bb_contentOffsetToScrollAnimated($model.contentOffsetToScrollAnimated) 62 | .bb_setupRefreshControl { refreshControl in 63 | refreshControl.tintColor = .blue 64 | refreshControl.attributedTitle = NSAttributedString(string: "Loading...", attributes: [.font: UIFont.systemFont(ofSize: 15), .foregroundColor: UIColor.blue]) 65 | } 66 | .bb_pullDownToRefresh(isRefreshing: $model.isRefreshing) { 67 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 68 | self.model.reloadListData() 69 | self.model.isRefreshing = false 70 | } 71 | } 72 | .bb_pullUpToLoadMore(bottomSpace: 30) { 73 | if self.model.isLoadingMore { return } 74 | self.model.isLoadingMore = true 75 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 76 | self.model.list = 0..