Giter Site home page Giter Site logo

bettersheet's Introduction

BetterSheet

Provides a powerful SwiftUI sheet replacement with the following features:

  • All the features of the built-in sheet modifiers, but more robust (tested against Xcode 11.0 beta 6).
  • Modal support (prevent the user from swiping to dismiss), similar to UIKit's modalInPresentation)
  • Support for invoking an action when the user tries to dismiss the sheet when it is modal.

Hopefully Apple will make the default sheet modifiers more robust and will add modal presentation support as well before the final version of iOS 13.0 is released, so this library becomes obsolete.

Basic Usage

First make sure you import the BetterSheet package and initialize UIHostingController with power sheet support in SceneDelegate.swift:

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

The basic API for presenting a sheet is similar to SwiftUI's sheet(isPresented:onDismiss:content:) view modifier. But instead of using sheet you use betterSheet.

For example:

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!")
            }
    }
}

For more advanced use-cases there is an API similar to SwiftUI's sheet(item:onDismiss:content: view modifier available:

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)")
            }
    }
}

Just as with the SwiftUI sheet modifier there is an environment value similar to SwiftUI's presentationMode available which you can use to dismiss a sheet from your own code. The BetterSheet version of this environment value is called betterSheetPresentationMode.

An example:

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()
            }
    }
}

Advanced usage

So far we've only looked at the API that offers similar functionality to the default SwiftUI sheet functionality. BetterSheet however offers some more advanced possibilities for if you don't want the user to simply dismiss your sheet with a swipe gesture.

For example:

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)
                }
        }
    }
}

License

This project is licensed under the terms of the MIT license. See the LICENSE file.

bettersheet's People

Contributors

petercv avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.