├── .gitignore ├── AppleWelcomeScreen.podspec ├── AppleWelcomeScreen.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Example ├── Example.xcodeproj │ └── project.pbxproj └── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ ├── ViewController.swift │ └── WelcomeScreenConfiguration+MyApp.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── WelcomeScreen+SwiftUI.swift ├── WelcomeScreenConfiguration.swift └── WelcomeScreenViewController.swift └── readme-images ├── example-plus.png ├── example-se.png ├── example-x.png └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | .swiftpm 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/macos,xcode,cocoapods,swiftpm 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,xcode,cocoapods,swiftpm 5 | 6 | ### CocoaPods ### 7 | ## CocoaPods GitIgnore Template 8 | 9 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 10 | # - Also handy if you have a large number of dependant pods 11 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 12 | Pods/ 13 | 14 | ### macOS ### 15 | # General 16 | .DS_Store 17 | .AppleDouble 18 | .LSOverride 19 | 20 | # Icon must end with two \r 21 | Icon 22 | 23 | 24 | # Thumbnails 25 | ._* 26 | 27 | # Files that might appear in the root of a volume 28 | .DocumentRevisions-V100 29 | .fseventsd 30 | .Spotlight-V100 31 | .TemporaryItems 32 | .Trashes 33 | .VolumeIcon.icns 34 | .com.apple.timemachine.donotpresent 35 | 36 | # Directories potentially created on remote AFP share 37 | .AppleDB 38 | .AppleDesktop 39 | Network Trash Folder 40 | Temporary Items 41 | .apdisk 42 | 43 | ### SwiftPM ### 44 | Packages 45 | .build/ 46 | xcuserdata 47 | DerivedData/ 48 | 49 | 50 | ### Xcode ### 51 | # Xcode 52 | # 53 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 54 | 55 | ## User settings 56 | xcuserdata/ 57 | 58 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 59 | *.xcscmblueprint 60 | *.xccheckout 61 | 62 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 63 | build/ 64 | *.moved-aside 65 | *.pbxuser 66 | !default.pbxuser 67 | *.mode1v3 68 | !default.mode1v3 69 | *.mode2v3 70 | !default.mode2v3 71 | *.perspectivev3 72 | !default.perspectivev3 73 | 74 | ## Gcc Patch 75 | /*.gcno 76 | 77 | ### Xcode Patch ### 78 | *.xcodeproj/* 79 | !*.xcodeproj/project.pbxproj 80 | !*.xcodeproj/xcshareddata/ 81 | !*.xcworkspace/contents.xcworkspacedata 82 | **/xcshareddata/WorkspaceSettings.xcsettings 83 | 84 | # End of https://www.toptal.com/developers/gitignore/api/macos,xcode,cocoapods,swiftpm 85 | -------------------------------------------------------------------------------- /AppleWelcomeScreen.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AppleWelcomeScreen' 3 | s.version = '2.1.0' 4 | s.summary = 'A super-simple welcome screen creator for iOS.' 5 | s.swift_version = '5.2' 6 | s.description = 'AppleWelcomeScreen is a super-simple way to create a welcome screen/onboarding experience similar to the ones used in built-in iOS apps.' 7 | s.homepage = 'https://github.com/Wilsonator5000/AppleWelcomeScreen' 8 | s.screenshots = 'https://raw.github.com/Wilsonator5000/AppleWelcomeScreen/master/readme-images/example-se.png', 'https://raw.github.com/Wilsonator5000/AppleWelcomeScreen/master/readme-images/example-x.png', 'https://raw.github.com/Wilsonator5000/AppleWelcomeScreen/master/readme-images/example-plus.png' 9 | s.license = { :type => 'MIT', :file => 'LICENSE' } 10 | s.author = { 'Wilson Gramer' => 'wilson@gramer.dev' } 11 | s.source = { :git => 'https://github.com/WilsonGramer/AppleWelcomeScreen.git', :tag => s.version.to_s } 12 | s.ios.deployment_target = '11.0' 13 | s.source_files = 'Sources/**/*' 14 | s.frameworks = 'UIKit' 15 | end 16 | -------------------------------------------------------------------------------- /AppleWelcomeScreen.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AppleWelcomeScreen.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C87C37BB254E1E47002407DB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87C37BA254E1E47002407DB /* AppDelegate.swift */; }; 11 | C87C37BD254E1E47002407DB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87C37BC254E1E47002407DB /* SceneDelegate.swift */; }; 12 | C87C37C7254E1E48002407DB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C87C37C5254E1E48002407DB /* LaunchScreen.storyboard */; }; 13 | C87C37D1254E1ED2002407DB /* AppleWelcomeScreen in Frameworks */ = {isa = PBXBuildFile; productRef = C87C37D0254E1ED2002407DB /* AppleWelcomeScreen */; }; 14 | C87C37D7254E1F2C002407DB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C87C37D6254E1F2C002407DB /* Assets.xcassets */; }; 15 | C87C37DB254E1F57002407DB /* WelcomeScreenConfiguration+MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87C37DA254E1F57002407DB /* WelcomeScreenConfiguration+MyApp.swift */; }; 16 | C87C37E2254E3ADF002407DB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87C37E1254E3ADF002407DB /* ViewController.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | C87C37B7254E1E47002407DB /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | C87C37BA254E1E47002407DB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | C87C37BC254E1E47002407DB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 23 | C87C37C6254E1E48002407DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | C87C37C8254E1E48002407DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | C87C37D6254E1F2C002407DB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | C87C37DA254E1F57002407DB /* WelcomeScreenConfiguration+MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WelcomeScreenConfiguration+MyApp.swift"; sourceTree = ""; }; 27 | C87C37E1254E3ADF002407DB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | C87C37B4254E1E47002407DB /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | C87C37D1254E1ED2002407DB /* AppleWelcomeScreen in Frameworks */, 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | C87C37AE254E1E47002407DB = { 43 | isa = PBXGroup; 44 | children = ( 45 | C87C37B9254E1E47002407DB /* Example */, 46 | C87C37B8254E1E47002407DB /* Products */, 47 | C87C37CF254E1ED2002407DB /* Frameworks */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | C87C37B8254E1E47002407DB /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | C87C37B7254E1E47002407DB /* Example.app */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | C87C37B9254E1E47002407DB /* Example */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | C87C37C8254E1E48002407DB /* Info.plist */, 63 | C87C37C5254E1E48002407DB /* LaunchScreen.storyboard */, 64 | C87C37D6254E1F2C002407DB /* Assets.xcassets */, 65 | C87C37BA254E1E47002407DB /* AppDelegate.swift */, 66 | C87C37BC254E1E47002407DB /* SceneDelegate.swift */, 67 | C87C37E1254E3ADF002407DB /* ViewController.swift */, 68 | C87C37DA254E1F57002407DB /* WelcomeScreenConfiguration+MyApp.swift */, 69 | ); 70 | path = Example; 71 | sourceTree = ""; 72 | }; 73 | C87C37CF254E1ED2002407DB /* Frameworks */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | ); 77 | name = Frameworks; 78 | sourceTree = ""; 79 | }; 80 | /* End PBXGroup section */ 81 | 82 | /* Begin PBXNativeTarget section */ 83 | C87C37B6254E1E47002407DB /* Example */ = { 84 | isa = PBXNativeTarget; 85 | buildConfigurationList = C87C37CB254E1E48002407DB /* Build configuration list for PBXNativeTarget "Example" */; 86 | buildPhases = ( 87 | C87C37B3254E1E47002407DB /* Sources */, 88 | C87C37B4254E1E47002407DB /* Frameworks */, 89 | C87C37B5254E1E47002407DB /* Resources */, 90 | ); 91 | buildRules = ( 92 | ); 93 | dependencies = ( 94 | ); 95 | name = Example; 96 | packageProductDependencies = ( 97 | C87C37D0254E1ED2002407DB /* AppleWelcomeScreen */, 98 | ); 99 | productName = Example; 100 | productReference = C87C37B7254E1E47002407DB /* Example.app */; 101 | productType = "com.apple.product-type.application"; 102 | }; 103 | /* End PBXNativeTarget section */ 104 | 105 | /* Begin PBXProject section */ 106 | C87C37AF254E1E47002407DB /* Project object */ = { 107 | isa = PBXProject; 108 | attributes = { 109 | LastSwiftUpdateCheck = 1220; 110 | LastUpgradeCheck = 1220; 111 | TargetAttributes = { 112 | C87C37B6254E1E47002407DB = { 113 | CreatedOnToolsVersion = 12.2; 114 | }; 115 | }; 116 | }; 117 | buildConfigurationList = C87C37B2254E1E47002407DB /* Build configuration list for PBXProject "Example" */; 118 | compatibilityVersion = "Xcode 9.3"; 119 | developmentRegion = en; 120 | hasScannedForEncodings = 0; 121 | knownRegions = ( 122 | en, 123 | Base, 124 | ); 125 | mainGroup = C87C37AE254E1E47002407DB; 126 | productRefGroup = C87C37B8254E1E47002407DB /* Products */; 127 | projectDirPath = ""; 128 | projectRoot = ""; 129 | targets = ( 130 | C87C37B6254E1E47002407DB /* Example */, 131 | ); 132 | }; 133 | /* End PBXProject section */ 134 | 135 | /* Begin PBXResourcesBuildPhase section */ 136 | C87C37B5254E1E47002407DB /* Resources */ = { 137 | isa = PBXResourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | C87C37D7254E1F2C002407DB /* Assets.xcassets in Resources */, 141 | C87C37C7254E1E48002407DB /* LaunchScreen.storyboard in Resources */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXResourcesBuildPhase section */ 146 | 147 | /* Begin PBXSourcesBuildPhase section */ 148 | C87C37B3254E1E47002407DB /* Sources */ = { 149 | isa = PBXSourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | C87C37BB254E1E47002407DB /* AppDelegate.swift in Sources */, 153 | C87C37BD254E1E47002407DB /* SceneDelegate.swift in Sources */, 154 | C87C37E2254E3ADF002407DB /* ViewController.swift in Sources */, 155 | C87C37DB254E1F57002407DB /* WelcomeScreenConfiguration+MyApp.swift in Sources */, 156 | ); 157 | runOnlyForDeploymentPostprocessing = 0; 158 | }; 159 | /* End PBXSourcesBuildPhase section */ 160 | 161 | /* Begin PBXVariantGroup section */ 162 | C87C37C5254E1E48002407DB /* LaunchScreen.storyboard */ = { 163 | isa = PBXVariantGroup; 164 | children = ( 165 | C87C37C6254E1E48002407DB /* Base */, 166 | ); 167 | name = LaunchScreen.storyboard; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXVariantGroup section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | C87C37C9254E1E48002407DB /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | ALWAYS_SEARCH_USER_PATHS = NO; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 180 | CLANG_CXX_LIBRARY = "libc++"; 181 | CLANG_ENABLE_MODULES = YES; 182 | CLANG_ENABLE_OBJC_ARC = YES; 183 | CLANG_ENABLE_OBJC_WEAK = YES; 184 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 185 | CLANG_WARN_BOOL_CONVERSION = YES; 186 | CLANG_WARN_COMMA = YES; 187 | CLANG_WARN_CONSTANT_CONVERSION = YES; 188 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 189 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 190 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 191 | CLANG_WARN_EMPTY_BODY = YES; 192 | CLANG_WARN_ENUM_CONVERSION = YES; 193 | CLANG_WARN_INFINITE_RECURSION = YES; 194 | CLANG_WARN_INT_CONVERSION = YES; 195 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 196 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 197 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 200 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 201 | CLANG_WARN_STRICT_PROTOTYPES = YES; 202 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 203 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 204 | CLANG_WARN_UNREACHABLE_CODE = YES; 205 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 206 | COPY_PHASE_STRIP = NO; 207 | DEBUG_INFORMATION_FORMAT = dwarf; 208 | ENABLE_STRICT_OBJC_MSGSEND = YES; 209 | ENABLE_TESTABILITY = YES; 210 | GCC_C_LANGUAGE_STANDARD = gnu11; 211 | GCC_DYNAMIC_NO_PIC = NO; 212 | GCC_NO_COMMON_BLOCKS = YES; 213 | GCC_OPTIMIZATION_LEVEL = 0; 214 | GCC_PREPROCESSOR_DEFINITIONS = ( 215 | "DEBUG=1", 216 | "$(inherited)", 217 | ); 218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 219 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 220 | GCC_WARN_UNDECLARED_SELECTOR = YES; 221 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 222 | GCC_WARN_UNUSED_FUNCTION = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | IPHONEOS_DEPLOYMENT_TARGET = 14.2; 225 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 226 | MTL_FAST_MATH = YES; 227 | ONLY_ACTIVE_ARCH = YES; 228 | SDKROOT = iphoneos; 229 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 230 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 231 | }; 232 | name = Debug; 233 | }; 234 | C87C37CA254E1E48002407DB /* Release */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 241 | CLANG_CXX_LIBRARY = "libc++"; 242 | CLANG_ENABLE_MODULES = YES; 243 | CLANG_ENABLE_OBJC_ARC = YES; 244 | CLANG_ENABLE_OBJC_WEAK = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 261 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 262 | CLANG_WARN_STRICT_PROTOTYPES = YES; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | COPY_PHASE_STRIP = NO; 268 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 269 | ENABLE_NS_ASSERTIONS = NO; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | GCC_C_LANGUAGE_STANDARD = gnu11; 272 | GCC_NO_COMMON_BLOCKS = YES; 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 = 14.2; 280 | MTL_ENABLE_DEBUG_INFO = NO; 281 | MTL_FAST_MATH = YES; 282 | SDKROOT = iphoneos; 283 | SWIFT_COMPILATION_MODE = wholemodule; 284 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 285 | VALIDATE_PRODUCT = YES; 286 | }; 287 | name = Release; 288 | }; 289 | C87C37CC254E1E48002407DB /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 293 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 294 | CODE_SIGN_STYLE = Automatic; 295 | DEVELOPMENT_TEAM = D42D4S8LQX; 296 | ENABLE_PREVIEWS = YES; 297 | INFOPLIST_FILE = Example/Info.plist; 298 | LD_RUNPATH_SEARCH_PATHS = ( 299 | "$(inherited)", 300 | "@executable_path/Frameworks", 301 | ); 302 | PRODUCT_BUNDLE_IDENTIFIER = dev.gramer.Example; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SWIFT_VERSION = 5.0; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | }; 307 | name = Debug; 308 | }; 309 | C87C37CD254E1E48002407DB /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 313 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 314 | CODE_SIGN_STYLE = Automatic; 315 | DEVELOPMENT_TEAM = D42D4S8LQX; 316 | ENABLE_PREVIEWS = YES; 317 | INFOPLIST_FILE = Example/Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/Frameworks", 321 | ); 322 | PRODUCT_BUNDLE_IDENTIFIER = dev.gramer.Example; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SWIFT_VERSION = 5.0; 325 | TARGETED_DEVICE_FAMILY = "1,2"; 326 | }; 327 | name = Release; 328 | }; 329 | /* End XCBuildConfiguration section */ 330 | 331 | /* Begin XCConfigurationList section */ 332 | C87C37B2254E1E47002407DB /* Build configuration list for PBXProject "Example" */ = { 333 | isa = XCConfigurationList; 334 | buildConfigurations = ( 335 | C87C37C9254E1E48002407DB /* Debug */, 336 | C87C37CA254E1E48002407DB /* Release */, 337 | ); 338 | defaultConfigurationIsVisible = 0; 339 | defaultConfigurationName = Release; 340 | }; 341 | C87C37CB254E1E48002407DB /* Build configuration list for PBXNativeTarget "Example" */ = { 342 | isa = XCConfigurationList; 343 | buildConfigurations = ( 344 | C87C37CC254E1E48002407DB /* Debug */, 345 | C87C37CD254E1E48002407DB /* Release */, 346 | ); 347 | defaultConfigurationIsVisible = 0; 348 | defaultConfigurationName = Release; 349 | }; 350 | /* End XCConfigurationList section */ 351 | 352 | /* Begin XCSwiftPackageProductDependency section */ 353 | C87C37D0254E1ED2002407DB /* AppleWelcomeScreen */ = { 354 | isa = XCSwiftPackageProductDependency; 355 | productName = AppleWelcomeScreen; 356 | }; 357 | /* End XCSwiftPackageProductDependency section */ 358 | }; 359 | rootObject = C87C37AF254E1E47002407DB /* Project object */; 360 | } 361 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | true 7 | } 8 | 9 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 10 | UISceneConfiguration( 11 | name: "Default Configuration", 12 | sessionRole: connectingSceneSession.role 13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | }, 6 | { 7 | "appearances" : [ 8 | { 9 | "appearance" : "luminosity", 10 | "value" : "dark" 11 | } 12 | ], 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UIApplicationSceneManifest 28 | 29 | UIApplicationSupportsMultipleScenes 30 | 31 | UISceneConfigurations 32 | 33 | UIWindowSceneSessionRoleApplication 34 | 35 | 36 | UISceneConfigurationName 37 | Default Configuration 38 | UISceneDelegateClassName 39 | $(PRODUCT_MODULE_NAME).SceneDelegate 40 | 41 | 42 | 43 | 44 | UIApplicationSupportsIndirectInputEvents 45 | 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Example/Example/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AppleWelcomeScreen 3 | 4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 5 | var window: UIWindow? 6 | 7 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 8 | guard let windowScene = scene as? UIWindowScene else { return } 9 | 10 | let window = UIWindow(windowScene: windowScene) 11 | window.rootViewController = ViewController() 12 | self.window = window 13 | window.makeKeyAndVisible() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AppleWelcomeScreen 3 | 4 | class ViewController: UIViewController { 5 | override func loadView() { 6 | self.view = UIView() 7 | self.view.backgroundColor = .systemBackground 8 | 9 | let button = UIButton(type: .system, primaryAction: UIAction { action in 10 | self.showWelcomeScreen() 11 | }) 12 | button.setTitle("Show Welcome Screen", for: .normal) 13 | self.view.addSubview(button) 14 | button.translatesAutoresizingMaskIntoConstraints = false 15 | NSLayoutConstraint.activate([ 16 | button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), 17 | button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), 18 | ]) 19 | } 20 | 21 | override func viewDidAppear(_ animated: Bool) { 22 | super.viewDidAppear(animated) 23 | 24 | self.showWelcomeScreen() 25 | } 26 | 27 | private func showWelcomeScreen() { 28 | self.present(WelcomeScreenViewController(configuration: .myApp), animated: true) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/Example/WelcomeScreenConfiguration+MyApp.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AppleWelcomeScreen 3 | 4 | extension WelcomeScreenConfiguration { 5 | static var myApp: WelcomeScreenConfiguration { 6 | WelcomeScreenConfiguration( 7 | appName: "My App", 8 | appDescription: "Lorem ipsum dolor sit amet, consecteteur adipiscing elit.", 9 | features: [ 10 | WelcomeScreenFeature( 11 | image: UIImage(systemName: "circle.fill")!, 12 | title: "Lorem ipsum", 13 | description: "Lorem ipsum dolor sit amet." 14 | ), 15 | WelcomeScreenFeature( 16 | image: UIImage(systemName: "square.fill")!, 17 | title: "Dolor sit amet", 18 | description: "Consecteteur adipiscing elit, sed do euismod tempor incdidunt." 19 | ), 20 | WelcomeScreenFeature( 21 | image: UIImage(systemName: "triangle.fill")!, 22 | title: "Consecteteur adipiscing elit, sed do euismod tempor incdidunt", 23 | description: "Lorem ipsum dolor sit amet, consecteteur adipiscing elit, sed do euismod tempor incdidunt ut labore et dolore magna aliqua." 24 | ), 25 | ] 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Wilson Gramer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "AppleWelcomeScreen", 7 | platforms: [.iOS("11.0")], 8 | products: [ 9 | .library(name: "AppleWelcomeScreen", targets: ["AppleWelcomeScreen"]), 10 | ], 11 | targets: [ 12 | .target(name: "AppleWelcomeScreen", path: "./Sources"), 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppleWelcomeScreen 2 | 3 | ![logo](readme-images/logo.png) 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/AppleWelcomeScreen.svg?style=flat)](https://cocoapods.org/pods/AppleWelcomeScreen) 6 | [![License](https://img.shields.io/cocoapods/l/AppleWelcomeScreen.svg?style=flat)](https://cocoapods.org/pods/AppleWelcomeScreen) 7 | [![Platform](https://img.shields.io/cocoapods/p/AppleWelcomeScreen.svg?style=flat)](https://cocoapods.org/pods/AppleWelcomeScreen) 8 | 9 | AppleWelcomeScreen is a super-simple way to create a welcome screen/onboarding experience similar to the ones used in built-in iOS apps. For example, here's the Notes welcome screen recreated using AppleWelcomeScreen: 10 | 11 | | iPhone SE | iPhone X | iPhone 8 Plus | 12 | | --- | --- | --- | 13 | | ![example-se.png](readme-images/example-se.png) | ![example-x.png](readme-images/example-x.png) | ![example-plus.png](readme-images/example-plus.png) | 14 | 15 | ## Usage 16 | 17 | Provide a configuration and you're good to go: 18 | 19 | ```swift 20 | let configuration = WelcomeScreenConfiguration( 21 | appName: "My App", 22 | appDescription: "Lorem ipsum dolor sit amet, consecteteur adipiscing elit.", 23 | features: [ 24 | WelcomeScreenFeature( 25 | image: UIImage(systemName: "circle.fill")!, 26 | title: "Lorem ipsum", 27 | description: "Lorem ipsum dolor sit amet." 28 | ), 29 | WelcomeScreenFeature( 30 | image: UIImage(systemName: "square.fill")!, 31 | title: "Dolor sit amet", 32 | description: "Consecteteur adipiscing elit, sed do euismod tempor incdidunt." 33 | ), 34 | WelcomeScreenFeature( 35 | image: UIImage(systemName: "triangle.fill")!, 36 | title: "Consecteteur adipiscing elit, sed do euismod tempor incdidunt", 37 | description: "Lorem ipsum dolor sit amet, consecteteur adipiscing elit, sed do euismod tempor incdidunt ut labore et dolore magna aliqua." 38 | ), 39 | ] 40 | ) 41 | 42 | // In your view controller: 43 | self.present(WelcomeScreenViewController(configuration: configuration), animated: true) 44 | 45 | // Or in SwiftUI: 46 | MyView().sheet(isPresented: self.$showWelcomeScreen) { 47 | WelcomeScreen(configuration: configuration) 48 | } 49 | ``` 50 | 51 | ## Example 52 | 53 | To run the example project, clone the repo and open `Example/Example.xcodeproj`. 54 | 55 | ## Installation 56 | 57 | CocoaPods: 58 | 59 | ```ruby 60 | pod 'AppleWelcomeScreen' 61 | ``` 62 | 63 | Swift Package Manager: 64 | 65 | ```swift 66 | .package(url: "https://github.com/WilsonGramer/AppleWelcomeScreen.git", from: "2.1.0") 67 | ``` 68 | 69 | ## Contributing 70 | 71 | AppleWelcomeScreen encourages contributions! [Create an issue](https://github.com/WilsonGramer/AppleWelcomeScreen/issues/new) or submit a pull request. 72 | -------------------------------------------------------------------------------- /Sources/WelcomeScreen+SwiftUI.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @available(iOS 13.0, *) 4 | public struct WelcomeScreen: UIViewControllerRepresentable { 5 | public let configuration: WelcomeScreenConfiguration 6 | 7 | public init(configuration: WelcomeScreenConfiguration) { 8 | self.configuration = configuration 9 | } 10 | 11 | public func makeUIViewController(context: Context) -> WelcomeScreenViewController { 12 | WelcomeScreenViewController(configuration: self.configuration) 13 | } 14 | 15 | public func updateUIViewController(_ uiViewController: WelcomeScreenViewController, context: Context) {} 16 | } 17 | -------------------------------------------------------------------------------- /Sources/WelcomeScreenConfiguration.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | public struct WelcomeScreenConfiguration { 5 | public var welcomeString: String 6 | public var appName: String 7 | public var welcomeAccessibilityLabel: String 8 | public var appDescription: String 9 | public var features: [WelcomeScreenFeature] 10 | public var continueButton: ContinueButtonConfiguration 11 | public var tintColor: UIColor? 12 | 13 | public init(welcomeString: String = NSLocalizedString("Welcome to", comment: "Welcome screen 'Welcome' string"), appName: String, welcomeAccessibilityLabel: String? = nil, appDescription: String, features: [WelcomeScreenFeature], continueButton: ContinueButtonConfiguration = .init(), tintColor: UIColor? = nil) { 14 | self.welcomeString = welcomeString 15 | self.appName = appName 16 | self.welcomeAccessibilityLabel = welcomeAccessibilityLabel ?? NSLocalizedString("\(welcomeString) \(appName)", comment: "Welcome screen accessibility label") 17 | self.appDescription = appDescription 18 | self.features = features 19 | self.continueButton = continueButton 20 | self.tintColor = tintColor 21 | } 22 | } 23 | 24 | public struct WelcomeScreenFeature { 25 | public var image: UIImage 26 | public var title: String 27 | public var description: String 28 | public var accessibilityLabel: String 29 | 30 | public init(image: UIImage, title: String, description: String, accessibilityLabel: String? = nil) { 31 | self.image = image 32 | self.title = title 33 | self.description = description 34 | self.accessibilityLabel = NSLocalizedString("\(title): \(description)", comment: "Welcome screen feature") 35 | } 36 | } 37 | 38 | public struct ContinueButtonConfiguration { 39 | public var title: String 40 | public var titleColor: UIColor 41 | public var action: (() -> Void)? 42 | 43 | public init(title: String = NSLocalizedString("Continue", comment: "Welcome screen continue button"), titleColor: UIColor = .white, action: (() -> Void)? = nil) { 44 | self.title = title 45 | self.titleColor = titleColor 46 | self.action = action 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/WelcomeScreenViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class WelcomeScreenViewController: UIViewController { 4 | public var configuration: WelcomeScreenConfiguration 5 | 6 | public init(configuration: WelcomeScreenConfiguration) { 7 | self.configuration = configuration 8 | super.init(nibName: nil, bundle: nil) 9 | } 10 | 11 | required init?(coder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | public override func loadView() { 16 | self.view = UIView() 17 | self.view.backgroundColor = .systemBackgroundCompat 18 | self.view.tintColor = self.configuration.tintColor 19 | 20 | let rootStackView = UIStackView() 21 | rootStackView.axis = .vertical 22 | rootStackView.spacing = 24 23 | rootStackView.alignment = .fill 24 | self.view.addSubview(rootStackView) 25 | rootStackView.translatesAutoresizingMaskIntoConstraints = false 26 | NSLayoutConstraint.activate([ 27 | self.view.safeAreaLayoutGuide.centerXAnchor.constraint(equalTo: rootStackView.centerXAnchor), 28 | self.view.safeAreaLayoutGuide.centerYAnchor.constraint(equalTo: rootStackView.centerYAnchor), 29 | self.view.safeAreaLayoutGuide.widthAnchor.constraint(equalTo: rootStackView.widthAnchor, multiplier: 1.2), 30 | self.view.safeAreaLayoutGuide.heightAnchor.constraint(equalTo: rootStackView.heightAnchor, multiplier: 1.15), 31 | ]) 32 | 33 | let scrollView = UIScrollView() 34 | scrollView.showsVerticalScrollIndicator = false 35 | rootStackView.addArrangedSubview(scrollView) 36 | scrollView.translatesAutoresizingMaskIntoConstraints = false 37 | 38 | let scrollViewContent = UIStackView() 39 | scrollViewContent.axis = .vertical 40 | scrollViewContent.spacing = 24 41 | scrollView.addSubview(scrollViewContent) 42 | scrollViewContent.translatesAutoresizingMaskIntoConstraints = false 43 | NSLayoutConstraint.activate([ 44 | scrollViewContent.topAnchor.constraint(equalTo: scrollView.topAnchor), 45 | scrollViewContent.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), 46 | scrollViewContent.widthAnchor.constraint(equalTo: scrollView.widthAnchor), 47 | ]) 48 | 49 | let headingStackView = UIStackView() 50 | headingStackView.axis = .vertical 51 | headingStackView.spacing = 8 52 | scrollViewContent.addArrangedSubview(headingStackView) 53 | headingStackView.translatesAutoresizingMaskIntoConstraints = false 54 | 55 | let titleStackView = UIStackView() 56 | titleStackView.axis = .vertical 57 | titleStackView.spacing = 4 58 | titleStackView.isAccessibilityElement = true 59 | titleStackView.accessibilityLabel = self.configuration.welcomeAccessibilityLabel 60 | headingStackView.addArrangedSubview(titleStackView) 61 | titleStackView.translatesAutoresizingMaskIntoConstraints = false 62 | 63 | let welcomeLabel = UILabel() 64 | welcomeLabel.text = self.configuration.welcomeString 65 | welcomeLabel.font = .preferredFont(for: .largeTitle, weight: .bold) 66 | welcomeLabel.adjustsFontForContentSizeCategory = true 67 | welcomeLabel.textColor = .labelCompat 68 | titleStackView.addArrangedSubview(welcomeLabel) 69 | welcomeLabel.translatesAutoresizingMaskIntoConstraints = false 70 | 71 | let appNameLabel = UILabel() 72 | appNameLabel.text = self.configuration.appName 73 | appNameLabel.font = .preferredFont(for: .largeTitle, weight: .bold) 74 | appNameLabel.adjustsFontForContentSizeCategory = true 75 | appNameLabel.textColor = self.view.tintColor 76 | appNameLabel.numberOfLines = 0 77 | titleStackView.addArrangedSubview(appNameLabel) 78 | appNameLabel.translatesAutoresizingMaskIntoConstraints = false 79 | 80 | let appDescriptionLabel = UILabel() 81 | appDescriptionLabel.text = self.configuration.appDescription 82 | appDescriptionLabel.font = .preferredFont(for: .title3, weight: .semibold) 83 | appDescriptionLabel.adjustsFontForContentSizeCategory = true 84 | appDescriptionLabel.textColor = .secondaryLabelCompat 85 | appDescriptionLabel.numberOfLines = 0 86 | headingStackView.addArrangedSubview(appDescriptionLabel) 87 | appDescriptionLabel.translatesAutoresizingMaskIntoConstraints = false 88 | 89 | let featuresStackView = UIStackView() 90 | featuresStackView.axis = .vertical 91 | featuresStackView.spacing = 18 92 | scrollViewContent.addArrangedSubview(featuresStackView) 93 | featuresStackView.translatesAutoresizingMaskIntoConstraints = false 94 | 95 | for feature in self.configuration.features { 96 | let featureView = FeatureView(for: feature) 97 | featuresStackView.addArrangedSubview(featureView) 98 | featureView.translatesAutoresizingMaskIntoConstraints = false 99 | } 100 | 101 | let continueButtonContainer = UIView() 102 | rootStackView.addArrangedSubview(continueButtonContainer) 103 | continueButtonContainer.translatesAutoresizingMaskIntoConstraints = false 104 | 105 | let continueButton = UIButton() 106 | continueButton.setTitle(self.configuration.continueButton.title, for: .normal) 107 | continueButton.setTitleColor(self.configuration.continueButton.titleColor, for: .normal) 108 | continueButton.addTarget(self, action: #selector(self.continueButtonTouchDown(_:)), for: .touchDown) 109 | continueButton.addTarget(self, action: #selector(self.continueButtonTouchUpOutside(_:)), for: .touchUpOutside) 110 | continueButton.addTarget(self, action: #selector(self.continueButtonTouchUpInside(_:)), for: .touchUpInside) 111 | continueButton.addTarget(self, action: #selector(self.continueButtonTouchCancel(_:)), for: .touchCancel) 112 | continueButton.clipsToBounds = true 113 | continueButton.layer.masksToBounds = true 114 | continueButton.layer.cornerRadius = 16 115 | continueButton.backgroundColor = self.view.tintColor 116 | continueButton.titleLabel!.font = .preferredFont(for: .body, weight: .semibold) 117 | continueButtonContainer.addSubview(continueButton) 118 | continueButton.translatesAutoresizingMaskIntoConstraints = false 119 | NSLayoutConstraint.activate([ 120 | continueButton.topAnchor.constraint(equalTo: continueButtonContainer.topAnchor), 121 | continueButton.bottomAnchor.constraint(equalTo: continueButtonContainer.bottomAnchor), 122 | continueButton.leadingAnchor.constraint(equalTo: continueButtonContainer.leadingAnchor), 123 | continueButton.trailingAnchor.constraint(equalTo: continueButtonContainer.trailingAnchor), 124 | continueButton.titleLabel!.topAnchor.constraint(equalTo: continueButton.topAnchor, constant: 16), 125 | continueButton.titleLabel!.bottomAnchor.constraint(equalTo: continueButton.bottomAnchor, constant: -16), 126 | ]) 127 | } 128 | 129 | @objc private func continueButtonTouchDown(_ continueButton: UIButton) { 130 | UIView.animate(withDuration: 0.25) { 131 | continueButton.layer.opacity = 0.75 132 | } 133 | } 134 | 135 | @objc private func continueButtonTouchUpOutside(_ continueButton: UIButton) { 136 | self.resetContinueButton(continueButton) 137 | } 138 | 139 | @objc private func continueButtonTouchUpInside(_ continueButton: UIButton) { 140 | self.resetContinueButton(continueButton) 141 | self.dismiss(animated: true) 142 | self.configuration.continueButton.action?() 143 | } 144 | 145 | @objc private func continueButtonTouchCancel(_ continueButton: UIButton) { 146 | self.resetContinueButton(continueButton) 147 | } 148 | 149 | private func resetContinueButton(_ continueButton: UIButton) { 150 | UIView.animate(withDuration: 0.25) { 151 | continueButton.layer.opacity = 1.0 152 | } 153 | } 154 | } 155 | 156 | private class FeatureView: UIView { 157 | init(for feature: WelcomeScreenFeature) { 158 | super.init(frame: .zero) 159 | 160 | self.isAccessibilityElement = true 161 | self.accessibilityLabel = feature.accessibilityLabel 162 | 163 | let imageView = UIImageView() 164 | imageView.image = feature.image 165 | imageView.contentMode = .scaleAspectFit 166 | self.addSubview(imageView) 167 | imageView.translatesAutoresizingMaskIntoConstraints = false 168 | let size = UIFontMetrics.default.scaledValue(for: 48) 169 | NSLayoutConstraint.activate([ 170 | imageView.topAnchor.constraint(equalTo: self.topAnchor), 171 | imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 172 | imageView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor), 173 | imageView.widthAnchor.constraint(equalToConstant: size), 174 | imageView.heightAnchor.constraint(greaterThanOrEqualToConstant: size), 175 | ]) 176 | 177 | let titleLabel = UILabel() 178 | titleLabel.text = feature.title 179 | titleLabel.font = .preferredFont(for: .body, weight: .semibold) 180 | titleLabel.textColor = .labelCompat 181 | titleLabel.numberOfLines = 0 182 | self.addSubview(titleLabel) 183 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 184 | NSLayoutConstraint.activate([ 185 | titleLabel.topAnchor.constraint(equalTo: self.topAnchor), 186 | titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 8), 187 | titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor), 188 | ]) 189 | titleLabel.setContentCompressionResistancePriority(.required, for: .vertical) 190 | 191 | let descriptionLabel = UILabel() 192 | descriptionLabel.text = feature.description 193 | descriptionLabel.font = .preferredFont(for: .body, weight: .regular) 194 | descriptionLabel.textColor = .secondaryLabelCompat 195 | descriptionLabel.numberOfLines = 0 196 | self.addSubview(descriptionLabel) 197 | descriptionLabel.translatesAutoresizingMaskIntoConstraints = false 198 | NSLayoutConstraint.activate([ 199 | descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor), 200 | descriptionLabel.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor), 201 | descriptionLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 8), 202 | descriptionLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor), 203 | ]) 204 | descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) 205 | } 206 | 207 | required init?(coder: NSCoder) { 208 | fatalError("init(coder:) has not been implemented") 209 | } 210 | } 211 | 212 | private extension UIColor { 213 | static var systemBackgroundCompat: UIColor { 214 | if #available(iOS 13.0, *) { 215 | return .systemBackground 216 | } else { 217 | return .white 218 | } 219 | } 220 | 221 | static var labelCompat: UIColor { 222 | if #available(iOS 13.0, *) { 223 | return .label 224 | } else { 225 | return .black 226 | } 227 | } 228 | 229 | static var secondaryLabelCompat: UIColor { 230 | if #available(iOS 13.0, *) { 231 | return .secondaryLabel 232 | } else { 233 | return .black 234 | } 235 | } 236 | } 237 | 238 | private extension UIFont { 239 | static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont { 240 | let metrics = UIFontMetrics(forTextStyle: style) 241 | let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) 242 | let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight) 243 | return metrics.scaledFont(for: font) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /readme-images/example-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WilsonGramer/AppleWelcomeScreen/e165321e27b60fefbffa463941bc97ffa2129085/readme-images/example-plus.png -------------------------------------------------------------------------------- /readme-images/example-se.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WilsonGramer/AppleWelcomeScreen/e165321e27b60fefbffa463941bc97ffa2129085/readme-images/example-se.png -------------------------------------------------------------------------------- /readme-images/example-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WilsonGramer/AppleWelcomeScreen/e165321e27b60fefbffa463941bc97ffa2129085/readme-images/example-x.png -------------------------------------------------------------------------------- /readme-images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WilsonGramer/AppleWelcomeScreen/e165321e27b60fefbffa463941bc97ffa2129085/readme-images/logo.png --------------------------------------------------------------------------------