├── .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 | 
4 |
5 | [](https://cocoapods.org/pods/AppleWelcomeScreen)
6 | [](https://cocoapods.org/pods/AppleWelcomeScreen)
7 | [](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 | |  |  |  |
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
--------------------------------------------------------------------------------