{"componentChunkName":"component---src-templates-article-js","path":"/article/swiftui-interactive-custom-sheet","result":{"data":{"contentfulArticle":{"title":"Interactive Custom Sheet","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1590177628/interactive-sheet/ztmqriywubkhgtiuhneu.png","createdDate":"Saturday, 19 October 2019","videoUrl":"xtUmaW5_AP8","updatedDate":"3 years ago","isNewLayout":null,"previews":null,"slug":"swiftui-interactive-custom-sheet","author":{"email":"john@liquidcoder.com","name":"John"},"tags":[{"title":"Article"},{"title":"Free"},{"title":"Swift UI"},{"title":"UI Design"}],"allChapters":null,"code":null,"explanation":null,"downloads":[{"name":"Project source code","url":"https://github.com/liquidcoder1/InteractiveCustomSheet"}],"resources":[{"name":"Apple developer documentation","url":"https://developer.apple.com/documentation/swiftui"},{"name":"Hacking with swift","url":"https://www.hackingwithswift.com"},{"name":"Swift by sundell","url":"https://www.swiftbysundell.com"}],"body":{"childMdx":{"tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#wiki-card","title":"Wiki Card"},{"url":"#viewmodifiers","title":"ViewModifiers"},{"url":"#wiki-app","title":"Wiki App"},{"url":"#interactive-sheet","title":"Interactive Sheet."},{"url":"#create-new-wiki-view","title":"Create New Wiki View"},{"url":"#wiki-button","title":"Wiki Button"},{"url":"#toolbarview","title":"ToolbarView"}]},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"In this post, I will show you how to build the above layout, but the main focus will be the interactive sheet. So I will divide this article into 2 sections,  the first section will be the normal layout, and the second will be the interactive sheet and Custom ToolBar.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 12 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Catalina or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 14 or greater\")), mdx(\"h2\", null, \"Get started\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Open Xcode\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Create a new Xcode project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Select single view app and click next\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Name your app and make sure the user interface is Swift UI\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Last, click Finish.\")), mdx(\"p\", null, \"Rename the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" file and the struct to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiApp\"), \" and make sure you rename its reference in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SceneDelegate\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {\\n        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\\n        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.\\n        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).\\n    \\n        // Create the SwiftUI view that provides the window contents.\\n        let wikiApp = WikiApp()\\n    \\n        // Use a UIHostingController as window root view controller.\\n        if let windowScene = scene as? UIWindowScene {\\n            let window = UIWindow(windowScene: windowScene)\\n            window.rootViewController = ::UIHostingController(rootView: weatherApp)::\\n            self.window = window\\n            window.makeKeyAndVisible()\\n        }\\n    }\\n\")), mdx(\"h2\", null, \"Wiki Card\"), mdx(\"p\", null, \"Now, create a new group (Folder) and  inside it create a swiftUI file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiCard\")), mdx(\"p\", null, \"Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" content with this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var icon = \\\"book.fill\\\"\\n    var color = \\\"orange\\\"\\n    var text = \\\"All Wikis\\\"\\n    \\n    var body: some View {\\n         HStack {\\n            VStack(alignment: .leading, spacing: 15 ){\\n                Image(systemName: icon).foregroundColor(Color.white)\\n                Text(text).foregroundColor(Color.white)\\n            }\\n            \\n            Spacer()\\n            Image(systemName: icon).resizable()\\n                .foregroundColor(Color.white.opacity(0.3))\\n                .frame(width: 40, height: 40)\\n                .aspectRatio(contentMode: .fit)\\n            \\n        }.padding()\\n            .frame(minWidth: 0.0, maxWidth: .infinity, alignment: .leading)\\n            .background(Color(color))\\n            .cornerRadius(15)\\n            .shadow(color: Color(color)\\n            .opacity(0.4), radius: 10, x: 0, y: 5)\\n        \\n       }\\n        \\n    }\\n\")), mdx(\"p\", null, \"And the preview should show this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571342824/interactive-sheet/lpdqg1q6kr1zmiekzeso.png\",\n    \"alt\": \"SwiftUI\"\n  })), \" \"), mdx(\"p\", null, \"If you didn\\u2019t copy the colours and images folders from the completed project, you might not see that. Feel free to use whatever colours you want\"), mdx(\"p\", null, \"The code is pretty self-explanatory, I have just put the book icon and the \\u2018All Wikis\\u2019 text into a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" which I align Horizontally with the big book icon pushed to extreme right edge with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Spacer()\")), mdx(\"h2\", null, \"ViewModifiers\"), mdx(\"p\", null, \"Next, create a new folder named Modifiers and inside it add a new swift file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavIconStyle\"), \". Put the following code in it:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct NavIconStyle: ViewModifier {\\n    func body(content: Content) -> some View {\\n        content.frame(width: 20, height: 20).foregroundColor(Color.black)\\n    }\\n}\\n\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"A ViewModifier is a modifier that you apply to a view or another view modifier, producing a new modified version of the original value.\")), mdx(\"p\", null, \"Defining that viewModifier will help us reuse the same code in more than one place. You will see how to use it shortly.\"), mdx(\"h2\", null, \"Wiki App\"), mdx(\"p\", null, \"Now, Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text(\\u201CHello World\\u201D)\"), \" in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiApp\"), \" body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" NavigationView {\\n            ScrollView(showsIndicators: false) {\\n                VStack(spacing: 20){\\n                    HStack(spacing: 20){\\n                        WikiCard()\\n                        WikiCard(icon: \\\"lock.fill\\\", color: \\\"blue\\\", text: \\\"Private\\\")\\n                    }\\n                    \\n                    HStack(spacing: 20){\\n                        WikiCard(icon: \\\"bookmark.fill\\\", color: \\\"purple\\\", text: \\\"Bookmarked\\\")\\n                        WikiCard(icon: \\\"macwindow\\\", color: \\\"green\\\", text: \\\"Templates\\\")\\n                    }\\n                    \\n                }\\n            }.padding(.horizontal)\\n                .padding(.bottom, 30)\\n                .navigationBarTitle(\\\"Wiki Lists\\\")\\n                .navigationBarItems(leading: Button(action: {\\n                }) {\\n                    Image(systemName: \\\"chart.bar\\\").resizable().modifier(NavIconStyle())\\n                    }, trailing: Button(action: {\\n                        \\n                    }){\\n                        Image(systemName: \\\"chart.pie\\\").resizable().modifier(NavIconStyle())\\n                })\\n        }\\n\")), mdx(\"p\", null, \"In the code above, I\\u2019ve created a 2 by 2 wikiCard grid by putting 2 HStacks containing 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiCard\"), \" each inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \"; then I surround the entire thing with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \", and last I surround the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \" with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationView\"), \" to create a navigationBar. Notice where I put the navigationBar modifiers. I didn\\u2019t add modifiers to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationView\"), \" itself; I\\u2019ve  added to its child which is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \". Now, look how I use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"modifer\"), \" on the Navigation Button images, as you can see I reuse the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavIconStyle\"), \"() on both buttons.  \"), mdx(\"p\", null, \"That\\u2019s it for this section\\u2026 The rest will be very simple to implement and you can add whatever you want. I just don\\u2019t want to make this article too long with unnecessary content. If you want to know how I did the rest of this screen, just download the completed project and take a look, the code is pretty simple and straight forward . Let\\u2019s implement the interactive sheet now.\"), mdx(\"h2\", null, \"Interactive Sheet.\"), mdx(\"p\", null, \"We will need to create a custom view and it will be like the built-in views, this way, we can reuse it wherever we want and be able to add child views to it as well. \"), mdx(\"p\", null, \"Now, create a new swiftUI file inside the views folder and name it \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SheetView\"), \".\\nChange the struct to be generic in order to be able to add child views to it, you will see how shortly. Change the struct signature to this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct SheetView<Content>: View where Content: View {\\n    \\n    var body: some View {\\n        Text(\\\"Hello World!\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, \"As you can see, we made  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SheetView\"), \" generic where the generic parameter must be a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"View\"), \".  After making that change, you will an error, but don\\u2019t worry we will fix that shortly. \"), mdx(\"p\", null, \"Add the following properties inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SheetView\"), \" above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Binding var currentHeight: CGFloat\\n@Binding var movingOffset: CGFloat\\nvar position = Position.up\\nvar smallHeight: CGFloat = 50\\nvar onDragEnd: ((_ position: Position)->()) = {_ in }\\nvar content: () -> Content\\n\")), mdx(\"p\", null, \"Then add this simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"enum\"), \" just above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SheetView\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"enum Position {\\n    case up\\n    case down\\n}\\n\")), mdx(\"p\", null, \"Let me give you a run-down of what those properties mean. \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"currentHeight\"), \" will keep track of the current height after the drag has ended\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"movingOffset\"), \" will keep track of the drag state, it will change and update the vertical \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Offset\"), \" of the view.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"position\"), \" will have 2 values based on the position of the view\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onDragEnd\"), \" will be called when the drag has ended.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"content\"), \" is a closure that will return a View.\\nReplace the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text(\\u201CHello World\\u201D)\"), \" in body with:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Group(content: self.content)\\n\")), mdx(\"p\", null, \"And Replace \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SheetView\"), \" call in  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SheetView_Previews\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"SheetView(currentHeight: .constant(0.0), movingOffset: .constant(0.0)) {\\n            Rectangle().foregroundColor(Color.red).frame(height: 500)\\n        }\\n\")), mdx(\"p\", null, \"I have set both the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"movingOffset\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"currentHeight\"), \" to 0.0 and  give the child view a red colour and make it 500px high.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview:\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571466316/interactive-sheet/mloahbz3xsd9dfkzkgao.png\",\n    \"alt\": \"Group in Swiftui\"\n  }))), mdx(\"p\", null, \"Now, add these modifiers to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Group\"), \" container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".frame(minHeight: 0.0, maxHeight: .infinity, alignment: .bottom)\\n                .offset(y: self.movingOffset)\\n                .gesture(\\n                DragGesture().onChanged({ drag in\\n                    if self.movingOffset >= 0{\\n                        self.movingOffset =  drag.translation.height  + self.currentHeight\\n                    }\\n                }).onEnded({ drag in\\n                    withAnimation( .spring(dampingFraction: 0.7) ) {\\n                        if  self.movingOffset > -50 {\\n                            self.movingOffset = 0.0\\n                            self.onDragEnded(.up)\\n                        }\\n                        \\n                        if drag.translation.height > 80 {\\n                            self.movingOffset =  self.smallHeight\\n                            self.onDragEnded(.down)\\n                        }\\n                        self.currentHeight = self.movingOffset\\n                    }\\n                })\\n        )\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Explanation:\")), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".frame(minHeight: 0.0, maxHeight: .infinity, alignment: .bottom)\"), \" : makes the parent View which is the group container fill the entire screen vertically and makes all children aligned to the bottom. Try removing the alignment value and you will see how it\\u2019s affecting the red rectangle inside.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".offset(y: self.movingOffset)\"), \":  I set the Y offset to the moving offset so that when we start dragging; the view will move by changing it\\u2019s Y offset.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".gesture(\\u2026)\"), \": This is where the magic happens.\\nIn \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onChanged\"), \", we check if the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"movingOffset\"), \" value is greater that or equal to 0 and perform the drag otherwise we don\\u2019t want to move the view up.  I add the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"currentHeight\"), \" value to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"movingOffset\"), \"  to adjust the initial position of the View. Try removing the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"currentHeight\"), \"  you will see that the view is always jumping to the initial position.\\nIn \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onEnded\"), \": Notice how I put the logic inside a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"withAnimation\"), \" block, this is because I want the view to animate back to the final height based on where the user lifted her or his finger.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".edgesIgnoringSafeArea(.all)\"), \": remove the bottom gap and makes the view fill the entire bottom and top  area.\\nIt\\u2019s now time to add our Custom View to the main screen to test the drag.\")), mdx(\"p\", null, \"Create a new SwiftUI file inside Views and named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomSheetView\"), \". Add these properties to the top of the file:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Binding  var currentHeight: CGFloat\\n@Binding var movingOffset: CGFloat\\n\")), mdx(\"p\", null, \"And replace the content inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" with this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" SheetView(currentHeight: self.$currentHeight, movingOffset: self.$movingOffset, smallHeight: 300, onDragEnd: { position in\\n            // Do things on drag End\\n        }) {\\n            ZStack(alignment: .top) {\\n               Text(\\\"We add views later\\\")\\n            }\\n            .background(Color.white)\\n            .shadow(color: Color.gray.opacity(0.2), radius: 6, x: 0.0, y: -5)\\n        }\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview:\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571472099/interactive-sheet/pxi6xcenbv1ikvoxrev9.png\",\n    \"alt\": \"SwiftUI Preview\"\n  }))), mdx(\"p\", null, \"Let\\u2019s now create the view that will replace that text. In Views folder, create a new SwiftUI file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CreateNewWikiView\"), \"  and replace the entire content with this:\"), mdx(\"h2\", null, \"Create New Wiki View\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct CreateNewWikiView: View {\\n    \\n    @Binding var movingOffset: CGFloat\\n    let screenSize = UIScreen.main.bounds.size\\n    @State private var textTitle = \\\"\\\"\\n    \\n    var body: some View {\\n        let screenWidth = screenSize.width\\n        let phoneRatio = String(format: \\\"%.3f\\\", screenSize.width / screenSize.height)\\n        let refRatio =   String(format: \\\"%.3f\\\",  9.0 / 16.0)\\n        let isXorAbove = phoneRatio != refRatio\\n        \\n        return VStack(spacing: 25) {\\n            //                    Spacer()\\n            Rectangle()\\n                .frame(width: 80, height: 7)\\n                .cornerRadius(5)\\n                .foregroundColor(Color.gray.opacity(0.5))\\n            Text(\\\"Create New Wiki\\\")\\n            \\n            VStack {\\n                TextField(\\\"Type a title\\\", text: self.$textTitle, onEditingChanged: { value in\\n                    print(value)\\n                }, onCommit: {\\n                    print(\\\"Finished editing\\\")\\n                }).multilineTextAlignment(.center)\\n                Divider().padding(.horizontal,50)\\n            }\\n            \\n            VStack(spacing: 15){\\n                WikiButton(icon: \\\"plus\\\", color: \\\"green\\\", text: \\\"Create New Wiki\\\", isFolder: false, action: {\\n                    print(\\\"Adding new wiki\\\")\\n                })\\n                WikiButton(icon: \\\"folder.fill.badge.plus\\\", color: \\\"green\\\", text: \\\"Create New Folder\\\", isFolder: true,action: {\\n                    print(\\\"Adding new folder\\\")\\n                })\\n            }\\n            \\n            Text(\\\"*Only visible to you, if you want you can share with others later\\\")\\n                .font(.footnote)\\n                .multilineTextAlignment(.center)\\n                .lineLimit(2)\\n                .frame(width: screenWidth * 0.8 )\\n                .foregroundColor(Color.gray)\\n            \\n        }.padding(.bottom, isXorAbove ? 60 : 50 )\\n            .padding(.top, 15)\\n    }\\n}\\n\\nstruct CreateNewWikiView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        CreateNewWikiView(movingOffset: .constant(0.0))\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"Wiki Button\"), mdx(\"p\", null, \"Create another swiftUI file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiButton\"), \" and replace everything inside with this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct WikiButton: View {\\n\\n    let screenSize = UIScreen.main.bounds.size\\n    var icon = \\\"plus\\\"\\n    var color = \\\"green\\\"\\n    var text = \\\"Create New Wiki\\\"\\n    var isFolder = false\\n    var action: (()->()) = {}\\n    \\n    var body: some View {\\n        let screenWidth = screenSize.width\\n        \\n        return Button(action: {\\n            self.action()\\n        }) {\\n            HStack {\\n                Image(systemName: icon)\\n                    .resizable()\\n                    .frame(width: 15, height: 15)\\n                    .foregroundColor(isFolder ? Color(color) : Color.white)\\n                \\n                Text(text)\\n                    .foregroundColor(isFolder ? Color(color) : Color.white)\\n                \\n            }.frame(width: screenWidth * 0.7 )\\n                .padding(.vertical)\\n                .background(Color(color).opacity(isFolder ? 0.2 : 1))\\n                .cornerRadius(10)\\n        }\\n    }\\n}\\n\\nstruct WikiButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        WikiButton()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code just create a button and styles it properly.\\nRefresh the preview by clicking the resume  button.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview:\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571479474/interactive-sheet/quwqybfzc7fi3rkc2epn.png\",\n    \"alt\": \"SwiftUI Preview\"\n  }))), mdx(\"p\", null, \"No need to explain this because the code is clear and self-explanatory. Just take a minute or two to read and understand what\\u2019s going on. \"), mdx(\"p\", null, \"Now, in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomSheetView\"), \", replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text(\\u2026)\"), \" with this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"CreateNewWikiView(movingOffset: self.$currentHeight)\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview:\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571480366/interactive-sheet/jw1mxtqrkwcogrtlb7sn.png\",\n    \"alt\": \"SwiftUI Preview\"\n  }))), mdx(\"p\", null, \"Let\\u2019s now integrate our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomSheetView\"), \" on the main screen. Add these 2 properties inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiApp\"), \" above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State private var currentHeight: CGFloat = 0.0\\n@State private var movingOffset: CGFloat = 0.0\\n\")), mdx(\"p\", null, \"Surround the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationView\"), \" with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" container and add this inside it below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationView\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"CustomSheetView(currentHeight: self.$currentHeight, movingOffset: self.$movingOffset)\\n\")), mdx(\"p\", null, \"You will now be able to drag the view up and down like this:\"), mdx(\"h2\", null, \"ToolbarView\"), mdx(\"p\", null, \"The step will be focused on adding some more awesomeness.  Add a new SwiftUI file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToolbarButton\"), \" inside  the Views folder and replace everything with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct ToolbarButton: View{\\n    \\n    var icon = \\\"bubble.left\\\"\\n    var isSelected = false\\n    var isPlus = false\\n    var action: (()->()) = {}\\n\\n    \\n    var body: some View{\\n        let selectedColor = \\\"orange\\\"\\n        \\n        return Button(action: {\\n            self.action()\\n        }) {\\n            HStack {\\n                Image(systemName: self.icon)\\n                    .resizable()\\n                    .aspectRatio(1, contentMode: .fit)\\n                    .frame(width: 30, height: 30)\\n                    .foregroundColor( isPlus ? Color.white : isSelected ? Color(selectedColor) : Color.gray)\\n                \\n            }.padding(10)\\n                .background(isPlus ? Color(\\\"green\\\") : isSelected ? Color(selectedColor).opacity(0.2) : Color.gray.opacity(0.2))\\n                .cornerRadius(10)\\n        }\\n    }\\n}\\n\\n\\nstruct ToolbarButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ToolbarButton()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code just creates and styles a simple button that will be reused to create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToolbarView\"), \":\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571482647/interactive-sheet/fsh7jwqzxo9fd6qbkfxq.png\",\n    \"alt\": \"ToolBar Button SwiftUI\"\n  }))), mdx(\"p\", null, \"Then create a new SwiftUI file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToolbarView\"), \" and replace everything inside with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ToolBarView: View {\\n    @Binding  var currentHeight: CGFloat\\n    @Binding var movingOffset: CGFloat\\n    \\n    var body: some View {\\n        HStack{\\n            ToolbarButton(icon: \\\"triangle\\\", isSelected: true) {}\\n            Spacer()\\n            ToolbarButton(icon: \\\"plus\\\", isPlus: true) {\\n                withAnimation(.spring(dampingFraction: 0.7)) {\\n                    self.currentHeight = 0.0\\n                    self.movingOffset = 0.0\\n                }\\n            }\\n            .shadow(color: Color(\\\"green\\\").opacity(0.5), radius: 5, x: 0.0, y: 10)\\n            Spacer()\\n            ToolbarButton( icon: \\\"bubble.left\\\", isSelected: false) {}\\n        }.padding(.top, 15)\\n            .padding(.horizontal, 30)\\n            .frame(minWidth: 0.0, maxWidth: .infinity)\\n    }\\n}\\n\\n\\nstruct ToolBarView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ToolBarView(currentHeight: .constant(0.0), movingOffset: .constant(0.0))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code creates a Horizontal stack of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToolbarButtons\"), \".\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1571483014/interactive-sheet/rfifdlvo3ddkrwiiuxzl.png\",\n    \"alt\": \"SwiftUI ToolbarView\"\n  }))), mdx(\"p\", null, \"Then, in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomSheetView\"), \" , add this below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CreateNewWikiView\"), \" call:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"ToolBarView(currentHeight: self.$currentHeight, movingOffset: self.$movingOffset)\\n\")), mdx(\"p\", null, \"Let\\u2019s now fix everything up. In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CreateNewWikiView\"), \", add this modifier to the outer \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" after the second padding modifier:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".offset(y: movingOffset )\\n\")), mdx(\"p\", null, \"And in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToolBarView\"), \", add this modifier to the outer \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" after \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"frame(\\u2026)\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".offset(y: self.currentHeight == 0  ? -100 : 0)\\n\")), mdx(\"p\", null, \"Those 2 modifiers will not make the design as we want it but it\\u2019s a step in the right direction. The last thing is to add the 2 rounded top corners:\"), mdx(\"p\", null, \"Create a new folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Shapes\"), \" and inside it create a swiftUI file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RoundedShape\"), \" and replace everything inside with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct RoundedShape: Shape {\\n    func path(in rect: CGRect) -> Path {\\n        let cornerRadius:CGFloat = 40\\n        var path = Path()\\n        \\n        path.move(to:  CGPoint(x: 0, y: cornerRadius))\\n        path.addQuadCurve(to: CGPoint(x: cornerRadius, y: 0), control: CGPoint.zero)\\n        path.addLine(to: CGPoint(x: rect.width - cornerRadius, y: 0))\\n        path.addQuadCurve(to: CGPoint(x: rect.width, y: cornerRadius), control: CGPoint(x: rect.width , y: 0))\\n        path.addLine(to: CGPoint(x: rect.width, y: rect.height))\\n        path.addLine(to: CGPoint(x: 0, y: rect.height))\\n        path.closeSubpath()\\n\\n        return path\\n    }\\n}\\n\")), mdx(\"p\", null, \"The code create a shape that we will use to clip the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomSheetView\"), \"\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" container.\"), mdx(\"p\", null, \"Add this modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomSheetView\"), \" after  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"background\"), \" .\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".clipShape(RoundedShape())\\n\")), mdx(\"p\", null, \"The good thing is that we\\u2019ve just killed 2 birds with 1 stone. We\\u2019ve added the top  rounded corners to the view and hidden the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToolBarView\"), \" hence creating the effect we wanted. Now, open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WikiApp\"), \" file and resume the preview. \"), mdx(\"p\", null, \"That\\u2019s it folks. Enjoy and subscribe for more.\"));\n}\n;\nMDXContent.isMDXComponent = true;","excerpt":"Introduction In this post, I will show you how to build the above layout, but the main focus will be the interactive sheet. So I will divide this article into 2 sections,  the first section will be the normal layout, and the second will be the interactive sheet and Custom ToolBar. Requirements Xcode…"}}}},"pageContext":{"slug":"swiftui-interactive-custom-sheet"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}