XUI Logo

XUI 是一个工具箱,用于使用 SwiftUI 创建模块化、可重用、可测试的应用程序架构。通过扩展来解决常见问题,XUI 使使用 SwiftUI 和 Combine 变得更加容易!

在我们的博客文章中

我们已经了解了如何在 SwiftUI 中组织视图和视图模型。 凭借所有这些知识,我们已在此库中组合和总结了最重要和最有用的组件。

🔥 功能特点

🏃‍♂️快速入门

Store

XUI 的组成部分之一是 Store 属性包装器。 它使得可以使用协议定义 SwiftUI 视图模型。

让我引导你完成这个过程:首先,我们为我们的视图模型创建一个协议,并使其符合 ViewModel

import XUI

protocol MyViewModel: ViewModel {

    // You can specify properties and methods as you like
    // This is just an example
    
    var text: String { get set }
    
    func open()
    
}

其次,我们为该协议创建一个实现。 我们的实现需要是一个符合 ObservableObject 和我们的协议的类。

import XUI

class DefaultMyViewModel: MyViewModel, ObservableObject {

    @Published var text: String
    
    func open() {
        // ...
    } 

}

最后但并非最不重要的一点,我们使用 Store 属性包装器在我们的视图中使用协议作为视图模型。

import XUI

struct MyView: View {
    @Store var viewModel: MyViewModel
    
    var body: some View {
        TextField("Text", text: $viewModel.text)
    }
}

正如你所看到的,你可以像在 SwiftUI 中使用 @ObservedObject 属性包装器一样使用你的视图模型。 你可以指定一个协议而不是被限制为具体的类型。 这样,我们可以编写 MyViewModel 协议的不同实现,并在 MyView 中使用它们。

深度链接

对于深度链接,我们在你的视图模型/协调器层级结构中提供了一个搜索算法。 你可以使用 DeepLinkable 协议来提供对你的直接子级的访问。 要在该层级结构中查找特定的子级,你可以使用 DeepLinkable 上的 firstReceiver 方法。

你可以在这篇博客文章中找到更详细的解释。

🤸‍♂️ 扩展

XUI 使使用 Combine 和 SwiftUI 更加容易!

Cancellable

当广泛使用 Combine 时,你的代码中可能会出现很多 .store(in: &cancellables)。 为了最大限度地减少代码大小并使代码更具可读性,我们提供了一个函数构建器,可以一次性在集合中插入多个 Cancellables。 让我们看看它的实际应用

var cancellables = Set<AnyCancellable>()

cancellables.insert {
    $myViewModel.title
        .sink { print("MyViewModel title changed to", $0) }

    $myViewModel.text
        .sink { print("MyViewModel text changed to", $0) }
}

Publisher

对于 Publishers,你通常使用 singles 或者简单的 publishers,它们只会发出单个值或错误。 为了使使用这些 publishers 更容易(并且由于 Result 类型现在是 Swift 的一部分),我们可以简单地构建以下扩展

var publisher: AnyPublisher<String, MyError>

publisher.asResult() // AnyPublisher<Result<String, MyError>, Never>
publisher.mapResult(success: { $0 }, failure: { _ in "Error occured." }) // AnyPublisher<String, Never>
publisher.tryMapResult(success: { $0 }, failure: { throw $0 }) // AnyPublisher<String, Error>

ViewModifiers

当在 SwiftUI 中使用 Coordinator 模式时(如这篇博客文章中所讨论的),我们需要将视图修饰符注入到子视图中,以便过渡逻辑完全由协调器视图指定,而不是分布在各个视图中。

提供了 NavigationModifierPopoverModifierSheetModifier,它们具有与实际修饰符类似的接口。

View

为了简化在 SwiftUI 中使用 NavigationView,我们提供了一个 onNavigation 方法,当你想在执行 NavigationLink 时执行闭包时可以使用该方法。 只需将其放在你的视图周围,它将自行添加 NavigationLink

此外,我们为你的视图添加了使用视图模型协议处理 sheetpopovernavigation 的方法。

示例

struct MyView: View {
    
    @Store var viewModel: MyViewModel
    
    var body: some View {
        NavigationView {
            Text("Example")
                .navigation(model: $viewModel.detailViewModel) { viewModel in
                    DetailView(viewModel: viewModel)
                }
                .sheet(model: $viewModel.sheetViewModel) { viewModel in
                    SheetView(viewModel: viewModel)
                }
        }
    }
    
}

Binding

使用 bindings,特别是当它涉及到集合时,是很困难的 - 但现在不再是了! 我们编写了一些扩展,可以轻松地使用 bindings 处理集合的元素。

var binding: Binding<[String]>

binding.first(equalTo: "example") // Binding<String?>
binding.first(where: { $0.count < 5 }) // Binding<String?>, this is not a practical example though

binding.first(equalTo: "example").forceUnwrap() // Binding<String>
binding.first(equalTo: "example").force(as: CustomStringConvertible.self) // Binding<CustomStringConvertible>

此外,人们可能希望更改或观察通过 binding 使用的值。

var binding: Binding<String>

binding.willSet { print("will set", $0) } 
// Binding<String>, will print whenever a new value is set by the binding, before it is forwarded to the initial binding

binding.didSet { print("did set", $0) } 
// Binding<String>, will print whenever a new value is set by the binding, after it is forwarded to the initial binding

binding.ensure { !$0.isEmpty }
// Binding<String>, will only set the initial binding, when the condition is fulfilled

binding.assert { !$0.isEmpty }
// Binding<String>, will assert on get and set, that a condition is fulfilled

binding.map(get: { $0.first! }, set: { String($0) })
// Binding<String>, will map the binding's value to a different type

binding.alterGet { $0.prefix(1) } 
// Binding<String>, will forward the altered value on get

binding.alterSet { $0.prefix(1) } 
// Binding<String>, will forward the altered value on set to the underlying binding

📚 示例

作为如何在你的应用程序中使用 XUI 的示例,我们借助 XUI 编写了一个 Recipes App

🛠 安装

Swift Package Manager

有关如何在你的应用程序中使用 Swift 包的更多信息,请参阅 这个 WWDC 演示文稿。 指定 https://github.com/quickbirdstudios/XUI.git 作为 XUI 包链接。

👨‍💻 作者

此框架由 QuickBird Studios 用 ❤️ 创建。

🤝 贡献

如果你需要帮助、发现错误或想要讨论功能请求,请打开一个 issue。 如果你想更改 XUI,请打开一个 PR。

📃 许可

XUI 在 MIT 许可下发布。 有关更多信息,请参阅 License.md