UIOnboarding

UIOnboarding 是一个动画的、可配置的欢迎屏幕,以 Swift Package 的形式呈现 – 其灵感来源于 Apple 的 Stocks 应用。

它支持运行 iOS 和 iPadOS 13 或更高版本的 iPhone、iPad 和 iPod touch,包括核心辅助功能,例如所有设备的动态字体、减少动态效果和旁白 – iPad 的分屏浏览和侧拉功能。

Lukman Aščić 开发和设计。


目录

预览

iPhone 和 iPod touch

默认 6.5" 默认 4"
UIOnboarding Preview 6.5 inch

iPad

12.9" 纵向 12.9" 横向
UIOnboarding Preview 12.9 inch Portrait UIOnboarding Preview 12.9 inch Landscape

分屏浏览

1/3 iPad 横向 1/2 iPad 横向 2/3 iPad 横向
UIOnboarding Slit View 1/3 Landscape UIOnboarding Slit View 1/2 Landscape UIOnboarding Slit View 2/3 Landscape
1/3 iPad 纵向 2/3 iPad 纵向
UIOnboarding Slit View 1/3 Portrait UIOnboarding Slit View 2/3 Portrait

侧拉

iPad 纵向 iPad 横向
UIOnboarding Slide Over Portrait UIOnboarding Slide Over Landscape

辅助功能

动态字体 旁白 减少动态效果
UIOnboarding Preview Dynamic Type UIOnboarding Preview VoiceOver UIOnboarding Preview Redcue Motion

安装

Swift Package Manager

在 Xcode 的包管理器中,在 File > Add Packages 下添加 https://github.com/lascic/UIOnboarding.git。从 2.0.0 版本或 main 分支中选择版本。

.package(url: "https://github.com/lascic/UIOnboarding.git", from: "2.0.0")
// or
.package(url: "https://github.com/lascic/UIOnboarding.git", branch: "main")

演示项目

/Demo 目录中可以找到三个演示项目,包括在 SwiftUI 应用中使用 UIOnboarding 的示例。

克隆仓库或将演示项目下载为 .zip 文件,以便在 Xcode 中的物理设备或模拟器上打开和运行它们。

在构建和运行项目之前,请确保使用您自己的配置描述文件进行设置。

用法

UIOnboardingViewController 接受一个 UIOnboardingViewConfiguration 参数用于设置。

UIKit

确保您要呈现的视图控制器嵌入在 UINavigationController 中。OnboardingViewController 已设置为全屏视图呈现。

// In the view controller you're presenting
import UIKit
import UIOnboarding

let onboardingController: UIOnboardingViewController = .init(withConfiguration: .setUp())
onboardingController.delegate = self
navigationController?.present(onboardingController, animated: false)

使用提供的委托方法关闭引导视图。

extension ViewController: UIOnboardingViewControllerDelegate {
    func didFinishOnboarding(onboardingViewController: UIOnboardingViewController) {
        onboardingViewController.modalTransitionStyle = .crossDissolve
        onboardingViewController.dismiss(animated: true, completion: nil)
    }
}

SwiftUI

SwiftUI 的 UIViewControllerRepresentable 协议使 UIKit 的 UIOnboardingViewController 表现为 SwiftUI 的 View

创建一个实现该协议的 OnboardingView 结构体,并使用 iOS 和 iPadOS 14 中引入的 .fullScreenCover() 修饰符在您要呈现的 SwiftUI 视图中显示它。

.fullScreenCover(isPresented: $showingOnboarding, content: {
    OnboardingView.init()
        .edgesIgnoringSafeArea(.all)
}

请注意,我们将 SwiftUI 的协调器指定为引导视图控制器的委托对象。

onboardingController.delegate = context.coordinator

完整设置

// In OnboardingView.swift
import SwiftUI
import UIOnboarding

struct OnboardingView: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIOnboardingViewController

    func makeUIViewController(context: Context) -> UIOnboardingViewController {
        let onboardingController: UIOnboardingViewController = .init(withConfiguration: .setUp())
        onboardingController.delegate = context.coordinator
        return onboardingController
    }
    
    func updateUIViewController(_ uiViewController: UIOnboardingViewController, context: Context) {}
    
    class Coordinator: NSObject, UIOnboardingViewControllerDelegate {
        func didFinishOnboarding(onboardingViewController: UIOnboardingViewController) {
            onboardingViewController.dismiss(animated: true, completion: nil)
        }
    }

    func makeCoordinator() -> Coordinator {
        return .init()
    }
}
// In ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var showingOnboarding = true
    
    var body: some View {
        NavigationView {
            Text("Hello, UIOnboarding!")
                .toolbar {
                    Button {
                        showingOnboarding = true
                    } label: {
                        Image(systemName: "repeat")
                    }
                }
                .fullScreenCover(isPresented: $showingOnboarding, content: {
                    OnboardingView.init()
                        .edgesIgnoringSafeArea(.all)
                })
        }
    }
}

struct ContentView_Previews: PreviewProvider { 
    static var previews: some View {
        ContentView.init()
    }
}

配置

UIOnboardingViewConfiguration 由六种非可选的组件类型组成。

  1. 应用图标,类型为 UIImage
  2. 第一行欢迎标题,类型为 NSMutableAttributedString
  3. 第二行欢迎标题,类型为 NSMutableAttributedString
  4. 核心功能,类型为 Array<UIOnboardingFeature>
  5. 提示文本,类型为 UIOnboardingTextViewConfiguration (例如:隐私政策、服务条款、作品集、网站)
  6. 继续标题,类型为 UIOnboardingButtonConfiguration

创建一个辅助结构体 UIOnboardingHelper 来定义这些组件,并将它们组合在 UIOnboardingViewConfiguration扩展 中。

示例

import UIKit
import UIOnboarding

struct UIOnboardingHelper {
    // App Icon
    static func setUpIcon() -> UIImage {
        return Bundle.main.appIcon ?? .init(named: "onboarding-icon")!
    }

    // First Title Line
    // Welcome Text
    static func setUpFirstTitleLine() -> NSMutableAttributedString {
        .init(string: "Welcome to", attributes: [.foregroundColor: UIColor.label])
    }
    
    // Second Title Line
    // App Name
    static func setUpSecondTitleLine() -> NSMutableAttributedString {
        .init(string: Bundle.main.displayName ?? "Insignia", attributes: [
            .foregroundColor: UIColor.init(named: "camou")!
        ])
    }

    // Core Features
    static func setUpFeatures() -> Array<UIOnboardingFeature> {
        return .init([
            .init(icon: .init(named: "feature-1")!,
                  title: "Search until found",
                  description: "Over a hundred insignia of the Swiss Armed Forces – each redesigned from the ground up."),
            .init(icon: .init(named: "feature-2")!,
                  title: "Enlist prepared",
                  description: "Practice with the app and pass the rank test on the first run."),
            .init(icon: .init(named: "feature-3")!,
                  title: "#teamarmee",
                  description: "Add name tags of your comrades or cadre. Insignia automatically keeps every name tag you create in iCloud.")
        ])
    }

    // Notice Text
    static func setUpNotice() -> UIOnboardingTextViewConfiguration {
        return .init(icon: .init(named: "onboarding-notice-icon")!,
                     text: "Developed and designed for members of the Swiss Armed Forces.",
                     linkTitle: "Learn more...",
                     link: "https://www.lukmanascic.ch/portfolio/insignia",
                     tint: .init(named: "camou"))
    }

    // Continuation Title
    static func setUpButton() -> UIOnboardingButtonConfiguration {
        return .init(title: "Continue",
                     titleColor: .white, // Optional, `.white` by default
                     backgroundColor: .init(named: "camou")!)
    }
}

欢迎标题本地化

如果欢迎标题只需要一行(例如,在另一种语言中),只需为任一参数提供一个空的 NSMutableAttributedString 值。UIOnboardingTitleLabelStack 会自动调整自身大小以适应相应的行数,无需额外更改。

以下是一个葡萄牙语的示例,第二行标题留空。

// First Title Line
// App Name
static func setUpFirstTitleLine() -> NSMutableAttributedString {
    return .init(string: Bundle.main.displayName ?? "Distintivos", attributes: [
            .foregroundColor: UIColor.init(named: "camou")!
    ])
}

// Second Title Line
// Empty
static func setUpSecondTitleLine() -> NSMutableAttributedString {
    return .init(string: "")
}

扩展

import UIOnboarding

extension UIOnboardingViewConfiguration {
    // UIOnboardingViewController init
    static func setUp() -> UIOnboardingViewConfiguration {
        return .init(appIcon: UIOnboardingHelper.setUpIcon(),
                     firstTitleLine: UIOnboardingHelper.setUpFirstTitleLine(),
                     secondTitleLine: UIOnboardingHelper.setUpSecondTitleLine(),
                     features: UIOnboardingHelper.setUpFeatures(),
                     textViewConfiguration: UIOnboardingHelper.setUpNotice(),
                     buttonConfiguration: UIOnboardingHelper.setUpButton())
    }
}

进一步设置

您可以借助 User Defaults 标志仅在首次应用启动时显示欢迎屏幕。请注意,未指定的 UserDefaults bool(forKey:) 键默认设置为 false

if !UserDefaults.standard.bool(forKey: "hasCompletedOnboarding") {
    showOnboarding()
}

在提供的委托方法中切换引导完成状态。

func didFinishOnboarding(onboardingViewController: OnboardingViewController) {
    onboardingViewController.modalTransitionStyle = .crossDissolve
    onboardingViewController.dismiss(animated: true) { 
        UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding")
    }
}

延伸阅读

版权

UIOnboarding 使用 MIT 许可证。

Insignia 应用图标和 Insignia 功能单元格资源版权归 Lukman Aščić 所有。保留所有权利。未经版权所有者事先书面许可,不得以任何方式复制或分发这些材料或其任何部分。