BetterSheet

提供强大的 SwiftUI Sheet 替代方案,具有以下特性:

希望 Apple 能让默认的 sheet 修饰符更加健壮,并且能在 iOS 13.0 的最终版本发布之前增加模态呈现支持,这样这个库就变得多余了。

基本用法

首先,请确保您导入了 BetterSheet 包,并在 SceneDelegate.swift 中初始化 UIHostingController,以获得强大的 Sheet 支持。

window.rootViewController = UIHostingController.withBetterSheetSupport(rootView: ContentView())

呈现 Sheet 的基本 API 类似于 SwiftUI 的 sheet(isPresented:onDismiss:content:) 视图修饰符。但您使用的是 betterSheet 而不是 sheet

例如

struct ContentView: View {
    @State var showDetail = false

    var body: some View {
        VStack {
            Button(action: { self.showDetail = true }) {
                Text("Show Detail")
            }
        }
            .betterSheet(isPresented: $showDetail) {
                Text("Detail!")
            }
    }
}

对于更高级的用例,有一个类似于 SwiftUI 的 sheet(item:onDismiss:content: 视图修饰符的 API 可用。

struct Fruit {
    let name: String
}

extension Fruit: Identifiable {
    var id: String {
        name
    }
}

struct ContentView: View {
    let fruits = [Fruit(name: "Apple"), Fruit(name: "Banana"), Fruit(name: "Orange")]
    @State var selectedFruit: Fruit? = nil

    var body: some View {
        List(fruits) { fruit in
            Button(action: { self.selectedFruit = fruit }) {
                Text(fruit.name)
            }
        }
            .betterSheet(item: $selectedFruit) { fruit in
                Text("You selected \(fruit.name)")
            }
    }
}

与 SwiftUI 的 sheet 修饰符一样,有一个类似于 SwiftUI 的 presentationMode 的环境值可用,您可以使用它从自己的代码中关闭 Sheet。BetterSheet 版本的此环境值称为 betterSheetPresentationMode

一个例子

struct DetailView: View {
    @Environment(\.betterSheetPresentationMode) var presentationMode
    
    var body: some View {
        Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
            Text("Dismiss")
        }
    }    
}

struct ContentView: View {
    @State var showDetail = false

    var body: some View {
        VStack {
            Button(action: { self.showDetail = true }) {
                Text("Show Detail")
            }
        }
            .betterSheet(isPresented: $showDetail) {
                DetailView()
            }
    }
}

高级用法

到目前为止,我们只研究了提供类似于默认 SwiftUI Sheet 功能的 API。但是,如果您不希望用户简单地通过滑动来关闭您的 Sheet,BetterSheet 提供了一些更高级的可能性。

例如

struct Fruit {
    let name: String
}

extension Fruit: Identifiable {
    var id: String {
        name
    }
}

struct EditView: View {
    @Binding var fruits: [Fruit]
    
    let fruit: Fruit?
    @State var name: String
    
    @Environment(\.betterSheetPresentationMode) var presentationMode
    
    @State var showDismissActions = false
    
    init(fruits: Binding<[Fruit]>, fruit: Fruit? = nil) {
        _fruits = fruits
        self.fruit = fruit
        _name = State(initialValue: fruit?.name ?? "")
    }
    
    var isNew: Bool {
        fruit == nil
    }
    
    var isValid: Bool {
        name.trimmingCharacters(in: .whitespaces).count > 0
    }
    
    var isModified: Bool {
        if let fruit = fruit, name != fruit.name {
            return true
        } else if fruit == nil && isValid {
            return true
        } else {
            return false
        }
    }
    
    var body: some View {
        NavigationView {
            Form {
                HStack {
                    Text("Name")
                    TextField("Fruit", text: $name).multilineTextAlignment(.trailing)
                }
            }
                .navigationBarTitle(fruit == nil ? "Add Fruit" : "Edit Fruit")
                .navigationBarItems(
                    leading: Button(action: save) { Text("Save").fontWeight(.bold).disabled(!isValid) },
                    trailing: Button(action: self.cancel) { Text("Cancel") }
                )
                .actionSheet(isPresented: $showDismissActions) {
                    ActionSheet(
                        title: Text("Select an option"),
                        message: nil,
                        buttons: [
                            .destructive(Text(isNew ? "Discard Fruit" : "Discard Changes"), action: self.cancel),
                            .default(Text(isNew ? "Add Fruit" : "Save Fruit"), action: self.save),
                            .cancel()
                        ]
                    )
                }
                .betterSheetIsModalInPresentation(isModified)
                .onBetterSheetDidAttemptToDismiss {
                    self.showDismissActions = true
                }
        }
    }

    func save() {
        guard isValid else { return }
        
        let fruit = Fruit(name: name)
        
        if let index = fruits.firstIndex(where: { $0.id == self.fruit?.id }) {
            fruits.remove(at: index)
            fruits.insert(fruit, at: index)
        } else {
            fruits.append(fruit)
        }
        
        presentationMode.wrappedValue.dismiss()
    }
    
    func cancel() {
        presentationMode.wrappedValue.dismiss()
    }
}

struct ContentView: View {
    @State var fruits: [Fruit] = [Fruit(name: "Apple")]

    @State var addFruit = false
    @State var editFruit: Fruit? = nil

    var body: some View {
        NavigationView {
            List(fruits) { fruit in
                Text(fruit.name)
                Spacer()
                Button(action: { self.editFruit = fruit }) {
                    Image(systemName: "pencil.circle")
                }
            }
                .listStyle(GroupedListStyle())
                .navigationBarTitle("Fruits")
                .navigationBarItems(
                    leading: Button(action: { self.addFruit = true }) { Text("Add") }
                )
                .betterSheet(isPresented: $addFruit) {
                    EditView(fruits: self.$fruits)
                }
                .betterSheet(item: $editFruit) { fruit in
                    EditView(fruits: self.$fruits, fruit: fruit)
                }
        }
    }
}

许可证

此项目根据 MIT 许可证的条款获得许可。 请参阅 LICENSE 文件。