iOS 版 Storybook

此软件包是一个微型框架,用于以类似 Storybook 的方式渲染组件的预览。它利用 objc 运行时和 SwiftUI 来使其使用尽可能无缝。即使你的应用程序没有使用 SwiftUI,也可以使用它,UIKit 应用程序也可以利用此框架(UIKit 辅助库)。

2.0.0 版本现已发布。这带来了一些重大的新功能和生活质量的改进。这些包括:

项目要求

演示项目

一个针对 iOS 11 的演示项目可以在这里找到。

演示视频

V2

Storybook.v2.mp4

V1

storybook-demo.mp4

目标

路线图

✅ 可配置组件

✅ 能够将 storybook 与 staging 版本一起发布,供设计师在应用程序旁边查看。目前正在 experimental 分支上进行此项工作。

🔲 使用快照进行视觉回归测试,如 storybook js:https://storybook.org.cn/tutorials/intro-to-storybook/react/en/test/

🔲 待定...

从 1.x.x 版本升级到 2.0.0

2.0 版本已发布,其中包含许多改进,但这样做可能会导致某些现有功能不再以相同的方式工作。

  1. MacOS 必须为 11+,以前建议使用,但仍然可以在 10.15 上运行。现在强制要求为 11+。
  2. StorybookPage 已更新为使用文件夹系统。旧的初始化器仍然存在,但已弃用。如果未更新,UI 看起来可能不够理想,但仍然可以运行

推荐设置

创建一个名为 Storybook 的文件,并向其添加一个预览提供程序以渲染 StorybookCollection

请注意,如果你的应用程序的最低版本低于 iOS 13,则需要使用 @available(iOS 13, *)。预览仍然会渲染,你也不会收到任何编译错误。如果你的目标是 iOS 13+,则无需放置 @available(iOS 13, *)

// Storybook.swift

#if DEBUG

import Storybook
import SwiftUI

@available(iOS 13, *)
struct StorybookPreview: PreviewProvider {
    
    static var previews: some View {
        Storybook.render()
    }
}

#endif 

为了向 storybook 添加页面,只需在 Storybook 类上创建扩展,其中包含你要渲染的视图。建议在组件所在的文件中添加这些扩展。这将使你更容易找到 storybook 中组件的位置,并且如果你完全删除组件文件,它将自动从 storybook 中删除。

重要提示:静态属性必须用 @objc 标记,才能被 Storybook 发现和渲染。

// SomeView.swift 

#if DEBUG

import Storybook

@available(iOS 13.0, *)
extension Storybook {
    @objc static let someView = StorybookPage(
        folder: "/Design System/Views/Some View",
        views: [
            SomeView().storybookTitle("Primary")
            SomeView().storybookTitle("Secondary")
        ]
    )
}

#endif

控件

使用 2.0.0+ 版本,你可以在 storybook 中渲染一个控制面板,该面板可以修改当前正在查看的视图。控件由 StorybookControlType 枚举提供支持,该枚举允许你使用预构建的控件或你自己的自定义控件。

为了利用控件,你必须将它们添加到你的 Storybook 上下文中。由于 storybook 利用了 SwiftUI 的 Environment,这意味着你可以将控件应用于单个视图或级联到 Storybook 中的每个视图。默认情况下,Storybook 会将所有视图包装在一个控件上下文中,该上下文从环境中拾取控件,因此你无需一次又一次地指定相同的控件。

func storybookSetGlobalControls(_ controls: StorybookControlType...) -> some View

要将控件应用于 Storybook 中的所有视图,请在根目录设置全局控件。在此示例中,将为所有视图应用 colorScheme、dynamicType、screenSize 和自定义控件的控件。

Storybook.render()
    .storybookSetGlobalControls(
        .colorScheme,
        .dynamicType,
        .screenSize,
        .custom(StorybookControl(id: "MyCustomControl", view: {
            CustomControl()
        }))
    )

要向视图添加单个控件,你可以使用另一个函数将控件添加到上下文中。以下代码将为此特定视图的控制菜单添加一个 jira 文档链接。

extension Storybook {
    @objc static let someView = StorybookPage(
        folder: "/Design System/Views/Some View",
        view: SomeView()
            .storybookAddControls(
                .documentationLink(
                    title: "Jira", 
                    url: "https://jira.com/123", 
                    icon: .jira
                )
            )
            .storybookTitle("Primary")
    )
}

自定义控件示例

可以轻松地将自定义控件添加到 Storybook 控件覆盖层。这是一个更改视图标题的控件示例。

重要提示:如果控件具有需要更新的状态(而不是文本),则必须在更改时更新 id。例如,对于切换,你必须在切换更改时更新 id。希望将来会有更简洁的解决方案。

// The View used in the App
@available(iOS 13.0, *)
struct SomeView: View {
    let title: String
    
    var body: some View {
        Text(title)
    }
}

// A wrapper around SomeView to control it
@available(iOS 13.0, *)
struct ControlledSomeView: View {
    @State var title = "Hello, World!"
    @State var isToggled = false

    var controlId: String {
        return "SomeViewControl" + isToggled.description
    }
    
    var body: some View {
        SomeView(title: title)
            .storybookAddControls(
                .custom(StorybookControl(
                    id: controlId,
                    view: {
                        VStack {
                            TextField("Title", text: $title)
                            Toggle(isOn: $isToggled) {
                                Text("Some Toggle")
                            }
                        }
                    }
                ))
            )
    }
}

@available(iOS 13.0, *)
extension Storybook {
    @objc static let someView = StorybookPage(
        folder: "/Views",
        view: ControlledSomeView().storybookTitle("Some View")
    )
}

模型

Storybook

Storybook 类使用 objc 运行时来镜像其类型为 StorybookPage 的静态属性,并为其生成预览。为了向 storybook 添加预览,请在 Storybook 上创建一个扩展,其中包含指向 StorybookPage 的静态属性。

StorybookPage

StorybookPage 类用于渲染你想要在 StorybookCollection 中显示的内容。

public init(
    folder directory: String,
    view: StoryBookView,
    file: String = #file
)
public init(
    folder directory: String,
    views: [StoryBookView],
    file: String = #file
)

有两种初始化器用于创建页面。标题和文件参数用于渲染 Storybook 页面列表。有时,代码中组件的名称不能准确描述它是什么。因此,标题可以是组件更易于理解的描述,而文件告诉你该组件在代码中的位置。该文件有一个默认参数,它将获取初始化器调用自的文件,或者你可以手动提供该文件(不推荐)。

StorybookView

StoryBookView 结构是一个 SwiftUI 包装视图,用于渲染 StorybookPage 中的视图。你提供一个标题和要渲染的视图。

View 有一个扩展,将其自身包装在 StorybookView 中,以实现更简洁的 API。

func storybookTitle(_ title: String, file: String = #file) -> StoryBookView

// Usage 

SomeView().storybookTitle("My View")

StorybookCollection

StorybookCollection 结构是一个 SwiftUI 视图,用于渲染所有 Storybook 页面。这应该在 PreviewProvider 中使用,以便你可以轻松浏览而无需运行任何内容。

Storybook 上的一个便捷函数 render 使记住如何渲染内容更容易一些。

Storybook.render() is the same as StorybookCollection()