{"id":8646,"date":"2021-05-27T17:30:03","date_gmt":"2021-05-27T14:30:03","guid":{"rendered":"https:\/\/serialcoder.dev\/?p=8646"},"modified":"2021-05-27T17:31:17","modified_gmt":"2021-05-27T14:31:17","slug":"presenting-sheets-in-swiftui","status":"publish","type":"post","link":"https:\/\/serialcoder.dev\/text-tutorials\/swiftui\/presenting-sheets-in-swiftui\/","title":{"rendered":"Presenting Sheets in SwiftUI"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">\u23f1 Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">mins<\/span><\/span>\n<p>A sheet in iOS is a system provided view that appears modally on top of any other currently displayed view. A sheet is by default empty, and it&#8217;s our job as developers to provide it with custom views and content. It constitutes a good solution in order to show additional information to users, ask for their input, or let them perform an operation without leaving entirely what they have been currently doing. Presenting sheets in SwiftUI is a fast and a no-brainer task, so read on to find out the few how-to guidelines that govern it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Presenting a sheet<\/h2>\n\n\n\n<p>A sheet in SwiftUI is available as a <em>view modifier<\/em>, which is usually applied to the outermost view object. Whether the sheet is presented or not is something we usually control with a boolean @State property declared locally in the view&#8217;s structure. Initially we set it to false, indicating that way that the sheet must be hidden. In order to make the sheet appear, we change its value to true when necessary or after a user&#8217;s interaction.<\/p>\n\n\n\n<p>A sheet view modifier expects two arguments; the first is the <em>binding value<\/em> of the property that controls its presented state. The second is a closure, where we provide the view that will be displayed as the sheet&#8217;s content.<\/p>\n\n\n\n<p>To get a taste of everything just described, consider the following simple example where a button&#8217;s tap triggers the presentation of a sheet:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3,10,13-15 decode:true \" >struct ContentView: View {\n    @State private var showSheet = false\n    \n    var body: some View {\n        Button(\"Present Sheet\") {\n        \n            \/\/ By setting true to showSheet the\n            \/\/ sheet will be presented.\n            showSheet = true\n        \n        }.buttonConfiguration()\n        .sheet(isPresented: $showSheet, content: {\n            Text(\"Hello World from Sheet!\")\n        })\n    }\n}<\/pre><\/div>\n\n\n\n<p>See that the default value of <code>showSheet<\/code> is false, and we change that in the button&#8217;s action closure. Also see how we integrate the sheet; it&#8217;s a view modifier applied to the button in this example, as that&#8217;s the only view here. The <code>$showSheet<\/code> binding value determines the sheet&#8217;s presented state, and the <code>content<\/code> closure is the place that contains whatever must be displayed to the user. In this case is just a simple text.<\/p>\n\n\n\n<p>The above simple implementation results to this:<\/p>\n\n\n\n<div class=\"wp-block-image is-style-default\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/swiftui-posts-images\/sheets_1_first_sheet.gif\" alt=\"Modal sheet is presented\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Dismissing a sheet<\/h2>\n\n\n\n<p>There are three ways to dismiss a sheet. The first and simplest one is provided by iOS, and requires no effort at all from the developer. All we have to do is to drag it towards bottom, and the sheet will go away.<\/p>\n\n\n\n<p>The second approach is mostly convenient when we implement sheet&#8217;s content right in its <code>content<\/code> closure. There, we simply change the <code>showSheet<\/code> value to false, and the sheet will be dismissed.<\/p>\n\n\n\n<p>In the following example, there is a button along with the text in the sheet. By tapping on it, the <code>showSheet<\/code> property becomes false:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:5-7 decode:true \" >.sheet(isPresented: $showSheet, content: {\n    Text(\"Hello World from Sheet!\")\n    \n    Button(\"Dismiss\") {\n        showSheet = false\n    }\n})<\/pre><\/div>\n\n\n\n<p>The third method in order to dismiss a sheet becomes handy when the sheet content is implemented in a different view structure. Suppose that we have the following view:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \" >struct SheetContentView: View {\n    var body: some View {\n        Text(\"Hello World from Sheet!\")        \n        Spacer().frame(height: 50)\n        \n        Button(\"Dismiss\") {\n\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p>To use it, we just need to create a <code>SheetContentView<\/code> instance in the sheet&#8217;s <code>content<\/code> closure:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \" >.sheet(isPresented: $showSheet, content: {\n    SheetContentView()\n})<\/pre><\/div>\n\n\n\n<p>Right now there is no way to dismiss the sheet, other than dragging it downwards. To make it disappear when tapping on the dismiss button above, it&#8217;s necessary to access the <code>presentationMode<\/code> value from SwiftUI&#8217;s environment using the <code>@Environment<\/code> property wrapper:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \" >struct SheetContentView: View {\n    @Environment(\\.presentationMode) var presentationMode\n    \n    var body: some View {\n        ...\n    }\n}<\/pre><\/div>\n\n\n\n<p>Then, we can dismiss the sheet by calling the <code>dismiss()<\/code> method through the <code>wrappedValue<\/code> of the <code>presentationMode<\/code> property:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \" >Button(\"Dismiss\") {\n    presentationMode.wrappedValue.dismiss()\n}<\/pre><\/div>\n\n\n\n<p>Alternatively, we can dismiss the sheet by passing the <em>binding value<\/em> of the <code>showSheet<\/code> property that controls the presented state to the sheet&#8217;s content view. To do that, we must declare a property marked with the @Binding property wrapper in that view first:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \" >struct SheetContentView: View {\n    @Binding var showSheet: Bool\n    \n    var body: some View {\n        ...\n    }\n}<\/pre><\/div>\n\n\n\n<p>Next, set false to <code>showSheet<\/code> in the button that dismisses the sheet:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \" >Button(\"Dismiss\") {\n    showSheet = false\n}<\/pre><\/div>\n\n\n\n<p>Lastly, initialize a <code>SheetContentView<\/code> instance passing the binding value of the <code>showSheet<\/code> state property:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3 decode:true \" >.sheet(isPresented: $showSheet, content: {\n    SheetContentView(showSheet: $showSheet)\n})<\/pre><\/div>\n\n\n\n<p>The last approach demonstrated right above is the one that requires the most effort, so unless you have a specific reason to use it, you&#8217;d better stick to the <code>presentationMode<\/code> property from the SwiftUI views&#8217; environment. It&#8217;s faster and easier.<\/p>\n\n\n\n<div class=\"wp-block-image is-style-default\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/serialcoder.dev\/swiftui-posts-images\/sheets_2_dismiss_sheet.gif?i=1834575641\" alt=\"Modal sheet is dismissed\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Sheet with full screen cover<\/h2>\n\n\n\n<p>Most of the times, the default sheet&#8217;s behavior that allows to dismiss it by dragging downwards is a convenient feature. However, there might be cases where it&#8217;s more suitable to avoid that, and dismiss the sheet programmatically after some user&#8217;s input or interaction.<\/p>\n\n\n\n<p>In order to manage that, it&#8217;s needed to present the sheet so it covers the full screen. Thankfully, that&#8217;s pretty easy to do; instead of using the <code>sheet(isPresented:content:)<\/code> view modifier, simply use the <code>fullScreenCover(isPresented:content:)<\/code> modifier:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:8-10 decode:true \" >struct ContentView: View {\n    @State private var showSheet = false\n    \n    var body: some View {\n        ...\n            \n        .fullScreenCover(isPresented: $showSheet, content: {\n            SheetContentView()\n        })\n    }\n}<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">One sheet for many occasions<\/h2>\n\n\n\n<p>Sometimes there might be occasions where you will want to present a sheet for more than one purposes, and obviously with different content. In such circumstances, declaring multiple sheet view modifiers in order to cover all cases is not recommended; you will also need an equal number of state properties to manage the presented state of the sheets.<\/p>\n\n\n\n<p>The best solution is to have one sheet view modifier only, and present the proper content conditionally in the sheet&#8217;s <code>content<\/code> closure. That way you will have one state property to control the sheets appearance, and a more elegant solution as you are going to see right next.<\/p>\n\n\n\n<p>Regarding the conditional presentation of sheet content, you can use either boolean properties, or follow another approach, which is my favorite one; to declare a custom enumeration with cases matching to each occasion that sheet should be presented for.<\/p>\n\n\n\n<p>The following example demonstrates how to do that:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift mark:3-5,7,12,16,20,24-28 decode:true \" >struct ContentView: View {\n    enum SheetContent {\n        case first, second, third\n    }\n    \n    @State private var sheetContent: SheetContent = .first\n    @State private var showSheet = false    \n    \n    var body: some View {\n        Button(\"Show first\") {\n            sheetContent = .first\n            showSheet = true\n        }\n        Button(\"Show second\") {\n            sheetContent = .second\n            showSheet = true\n        }\n        Button(\"Show third\") {\n            sheetContent = .third\n            showSheet = true\n        }\n        .sheet(isPresented: $showSheet, content: {\n            switch sheetContent {\n                case .first: FirstSheetContentView()\n                case .second: SecondSheetContentView()\n                case .third: ThirdSheetContentView()\n            }\n            \n        })\n    }\n}<\/pre><\/div>\n\n\n\n<p><code>SheetContent<\/code> is a custom type with cases representing the content that the sheet should contain, and <code>sheetContent<\/code> property is of that type. Depending on which one of the three buttons is tapped, <code>sheetContent<\/code> gets the proper value that indicates the view that should be displayed when the sheet will appear.<\/p>\n\n\n\n<p>In the sheet&#8217;s <code>content<\/code> closure, there is a <code>switch<\/code> statement that checks the value of the <code>sheetContent<\/code> property, and depending on the case, it initializes the proper view.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Performing actions on sheet&#8217;s dismiss event<\/h2>\n\n\n\n<p>In certain cases you may need to perform additional actions right after the sheet has been dismissed. To manage that, it&#8217;s necessary to use another initializer for the sheet view modifier, called <code>sheet(isPresented:onDismiss:content:)<\/code>.<\/p>\n\n\n\n<p>The <code>onDismiss<\/code> parameter is a closure that gets called right upon the sheet&#8217;s dismissal. That&#8217;s the place to add any tasks you want to perform once that event has occurred.<\/p>\n\n\n\n<p>The following example prints a message right when the sheet is dismissed:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:swift decode:true \" >.sheet(isPresented: $showSheet, onDismiss: {\n    print(\"Goodbye sheet!\")\n}, content: {\n    SheetContentView()\n})<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>As you can see, presenting sheets in SwiftUI is not a difficult task. In this post I shared with you pretty much everything you need to know about them; how to present, dismiss, and provide content to a sheet, as well as a few useful tips that will make things easier for you. Modal sheets can be proved a handy tool when building apps, so why not using them when that seems appropriate? Thank you for reading!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Read about presenting sheets in SwiftUI, how to deal with them, and discover some useful tips that you will often need to put in motion.<\/p>\n","protected":false},"author":1,"featured_media":8687,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[410],"tags":[305,519,520,362],"class_list":["post-8646","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-swiftui","tag-modal","tag-modal-sheet","tag-present-sheet","tag-swiftui"],"_links":{"self":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts\/8646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/comments?post=8646"}],"version-history":[{"count":0,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/posts\/8646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/media\/8687"}],"wp:attachment":[{"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/media?parent=8646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/categories?post=8646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/serialcoder.dev\/wp-json\/wp\/v2\/tags?post=8646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}