{"componentChunkName":"component---src-templates-article-js","path":"/article/swiftui-onboarding-screen","result":{"data":{"contentfulArticle":{"title":"Onboarding Screen","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1597490801/habitApp/q08rhufvoex5jv079qns.jpg","createdDate":"Monday, 09 December 2019","videoUrl":"https://www.youtube-nocookie.com/embed/MUsofD-uvtw","updatedDate":"3 years ago","isNewLayout":null,"previews":null,"slug":"swiftui-onboarding-screen","author":{"email":"john@liquidcoder.com","name":"John"},"tags":[{"title":"Article"},{"title":"Swift UI"},{"title":"UI Design"}],"allChapters":null,"code":null,"explanation":null,"downloads":[{"name":"Project source code","url":"https://github.com/liquidcoder1/OnboardingScreen/archive/master.zip"}],"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":"#view-controller","title":"View Controller"},{"url":"#coordinator","title":"Coordinator"},{"url":"#models","title":"Models"},{"url":"#page-view","title":"Page View"},{"url":"#page-view-container","title":"Page View Container"},{"url":"#page-indicator","title":"Page Indicator"},{"url":"#custom-button","title":"Custom button"}]},"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, \"We all know how awesome swiftUI is, but as great as it is, it's still young and missing some important features. So from time to time you may need to tap into the great UIKit framework to use some of its amazing features. Likely for us, there's a way of integrating UIKit views and view controlllers in SwiftUI. The process is a little bit\\nverbose but it gets the job done.\"), mdx(\"p\", null, \"In this article, I will show you  how to create an onboarding screen using UIPageViewController which is a UIKit feature. I will also follow-up this tutorial with a login and sign up screens, so make to subscribe to get notified when the article comes out.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 11 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 12 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(\"h2\", null, \"View Controller\"), mdx(\"p\", null, \"Create a folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ViewControllers\"), \" and add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PageViewController.swift\"), \"  inside containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nstruct PageViewController: UIViewControllerRepresentable {\\nfunc makeUIViewController(context: Context) -> UIPageViewController \\nfunc updateUIViewController(_ pageViewController: UIPageViewController, context: UIViewControllerRepresentableContext<PageViewController>) \\n}\\n\\n\")), mdx(\"p\", null, \"To use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PageViewController\"), \" in swiftUI, it needs to conform to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UIViewControllerRepresentable\"), \" protocol which will require you to override 2 methods which I explain below.\"), mdx(\"p\", null, \"\\u2022 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeUIViewController\"), \": This is where you will initialise the controller that you want to use in your swiftUI app and return it. In our case we are returning a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UIPageViewController\"), \" type.\\n\\u2022 \\xA0 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updateUIViewController\"), \": This is where you can make any update to your controller once it has been created in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeUIViewController\"), \".\"), mdx(\"p\", null, \"Those 2 methods are not the only ones that you can override, we will override on more methods and the fourth one is not needed in this tutorial.\"), mdx(\"p\", null, \"Let's now update the first 2 methods, but before that, add the following properties at the top of the file.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var controllers: [UIViewController]\\n@Binding var currentPage: Int\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeUIViewController\"), \" methods, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nlet pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)\\npageViewController.dataSource = context.coordinator\\npageViewController.delegate = context.coordinator\\ncontrollers.forEach($0.view.backgroundColor = UIColor(named: \\\"bgColor\\\") )\\npageViewController.view.backgroundColor = UIColor(named: \\\"bgColor\\\")\\nreturn pageViewController\\n\\n\")), mdx(\"p\", null, \"Like I said earlier, we will make all the initial setups in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"make\"), \" methods. The above code does the following:\\n\\u2022 We initialise the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pageViewControlller\"), \"\\n\\u2022 We set the datasource and delegate to the context's coordinator which we will create  shortly\\n\\u2022 And last, I just set background colours to all the pages.\"), mdx(\"p\", null, \"The in the updateUIViewController, add the following line of code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"pageViewController.setViewControllers(controllers[currentPage], direction: .forward, animated: true)\\n\\n\")), mdx(\"p\", null, \"In the above code block, I update the page controller to show the current page after the user has scrolled.\"), mdx(\"h2\", null, \"Coordinator\"), mdx(\"p\", null, \"The coordinator will be the class that will coordinate, as its name implies, the interaction between swiftUI with whatever controller we are using. This is the class that will handle all the datasource and delegate conformance, that's why we set the page controller's delegate and datasource to the parent coordinator.\"), mdx(\"p\", null, \"Now create a new class inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PageController\"), \" struct and name it Coordinator like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {\\n        \\n        var parent: PageViewController\\n        \\n        init(_ controller: PageViewController) {\\n            self.parent = controller\\n        }\\n        \\n        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {\\n            guard let index  = parent.controllers.firstIndex(of: viewController) else {\\n                return nil\\n            }\\n            \\n            if index == 0 {\\n                return nil\\n            }\\n            return parent.controllers[index - 1]\\n        }\\n        \\n        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {\\n            guard let index  = parent.controllers.firstIndex(of: viewController) else {\\n                return nil\\n            }\\n            \\n            if index == parent.controllers.count - 1{\\n                return nil\\n            }\\n                      \\n            return parent.controllers[index + 1]\\n        }\\n        \\n        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {\\n            if completed,\\n                let visibleViewController = pageViewController.viewControllers?.first,\\n                let index = parent.controllers.firstIndex(of: visibleViewController) {\\n                parent.currentPage = index\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"This code is pretty long, so let\\u2019s break it down:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first conform  the Coordinator to the page datasource and delegate like a normal UIViewController\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create a property that will keep a reference to the parent struct that will be set when the coordinator is instantiated.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then implement the first 2  datasource methods to tell the controller which page to show from the parent\\u2019s controllers array\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And last, in the delegate method we capture the index that the user has scrolled to and set it the parent\\u2019s currentPage which is a Binding property that will be used to update the user interface in SwiftUI.\\nYou now need to add the following code in the parent struct:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func makeCoordinator() -> PageViewController.Coordinator {\\n        Coordinator(self)\\n    }\\n    \\n\")), mdx(\"p\", null, \"This method needs to be called exactly like that and its job is to instantiate the coordinator.\"), mdx(\"h2\", null, \"Models\"), mdx(\"p\", null, \"Create a Models folder with a file name \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Page.swift\"), \" inside containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport Foundation\\n\\nstruct Page: Identifiable {\\n    \\n    let id: UUID\\n    let image: String\\n    let heading: String\\n    let subSubheading: String\\n    \\n    static var getAll: [Page] {\\n        [\\n            Page(id: UUID(), image: \\\"screen-1\\\", heading: \\\"Form new habits\\\", subSubheading: \\\"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna.\\\"),\\n            Page(id: UUID(), image: \\\"screen-2\\\", heading: \\\"Keep track of your progress\\\", subSubheading: \\\"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna.\\\"),\\n            Page(id: UUID(), image: \\\"screen-3\\\", heading: \\\"Setup your goals\\\", subSubheading: \\\"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna.\\\"),\\n            Page(id: UUID(), image: \\\"screen-4\\\", heading: \\\"Keep track of your progress\\\", subSubheading: \\\"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna.\\\")\\n            \\n        ]\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"Page View\"), mdx(\"p\", null, \"Let\\u2019s now create the page view that will be contained in a controller. Before you start, make sure you\\u2019ve downloaded the completed project and copy all images and colours into your project.\"), mdx(\"p\", null, \"Create a Views folder and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PageView.swift\"), \" with the following content inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct PageView: View {\\n    \\n    var page = Page.getAll.first!\\n    \\n    var body: some View {\\n            VStack{\\n                \\n                Image(\\\"screen-1\\\")\\n                VStack{\\n                    Text(page.heading).font(.title).bold().layoutPriority(1).multilineTextAlignment(.center)\\n                    Text(page.subSubheading)\\n                        .multilineTextAlignment(.center)\\n                }.padding()\\n            }\\n        \\n    }\\n}\\n\\nstruct PageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PageView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code is simple, it  just creates views stacked horizontally.\\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/v1575700133/habitApp/fuyqy3agl6qeutxqhki5.png\",\n    \"alt\": \"Swift UI App\"\n  }))), mdx(\"h2\", null, \"Page View Container\"), mdx(\"p\", null, \"This is the View that will package page views and send them to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PageViewController\"), \" and we will make it generic so that you can reuse it however you want.\\nNow add the following code in the file:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct PageViewContainer<T: View>  : View {\\n    \\n    var viewControllers: [UIHostingController<T>]\\n    @State var currentPage = 0\\n    \\n    var body: some View {\\n        \\n        return VStack {\\n            PageViewController(controllers: viewControllers, currentPage: self.$currentPage)\\n            \\n            PageIndicator(currentIndex: self.currentPage)\\n            \\n            LCButton(text: currentPage == viewControllers.count - 1 ? \\\"Get started\\\" : \\\"Next\\\") {\\n                if self.currentPage < self.viewControllers.count - 1{\\n                    self.currentPage += 1\\n                }\\n            }.padding()\\n            \\n        }.background(Color.backgroundColor)\\n    }\\n}\\n\\nstruct PageViewContainer_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PageViewContainer(viewControllers: Page.getAll.map({  UIHostingController(rootView: PageView(page: $0) )  }))\\n    }\\n}\\n\\u2028\\n\")), mdx(\"p\", null, \"Let\\u2019s go through this code step by step:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We add an array of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HostingControllers\"), \" property because in order to use a swiftUI view in UIKit, we need to create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HostingController\"), \" instance with it. So this will be an array of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UIHostingControllers\"), \" where their rootViews are \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PageView\"), \" instances.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then we add the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PageViewController\"), \" , the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PageIndicator\"), \" that we still need to create and a next button that we will also create shortly.\\nIn the Views folder, add the a file named \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"PageIndicator.swift\"), \" containing the following code:\")), mdx(\"h2\", null, \"Page Indicator\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct PageIndicator: View {\\n    \\n    var currentIndex: Int = 0\\n    var pagesCount: Int = 4\\n    var onColor: Color = Color.accentColor\\n    var offColor: Color = Color.offColor\\n    var diameter: CGFloat = 10\\n    \\n    var body: some View {\\n        HStack{\\n            ForEach(0..<pagesCount){ i in\\n                Image(systemName: \\\"circle.fill\\\").resizable()\\n                    .foregroundColor( i == self.currentIndex ? self.onColor : self.offColor)\\n                    .frame(width: self.diameter, height: self.diameter)\\n\\n            }\\n        }\\n    }\\n}\\n\\nstruct PageIndicator_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PageIndicator()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Like all views I create, I make sure you can customise colours and size of this view. Beside that, the code is pretty simple\\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/v1575702418/habitApp/qrhlew1vkeyhlgz3o6qh.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"In the same folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LCButton.swift\"), \" with the following code inside:\"), mdx(\"h2\", null, \"Custom button\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct LCButton: View {\\n    var text = \\\"Next\\\"\\n    var action: (()->()) = {}\\n    \\n    var body: some View {\\n      Button(action: {\\n        self.action()\\n      }) {\\n        HStack {\\n            Text(text)\\n                .bold()\\n                .frame(minWidth: 0, maxWidth: .infinity)\\n                .padding(.vertical)\\n                .accentColor(Color.white)\\n                .background(Color(\\\"accentColor\\\"))\\n                .cornerRadius(30)\\n            }\\n        }\\n    }\\n}\\n\\nstruct LCButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LCButton()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1575702419/habitApp/vkurzherkb8pui6oqabb.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"With that out of the way, if you go and resume the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PageViewContainer\"), \" preview, you should get this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1575703182/habitApp/xflqnuvgg7pwxktjxi7l.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now put this in the contentView\\u2019s body and you should be able to run the app and scroll or click the button to cycle through  the pages.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        VStack {\\n            PageViewContainer( viewControllers: Page.getAll.map({  UIHostingController(rootView: PageView(page: $0) ) }))\\n        }.frame(maxHeight: .infinity).background(Color.backgroundColor).edgesIgnoringSafeArea(.all)           \\n\")), mdx(\"p\", null, \"You can also switch to dark mode and should still look awesome.\"), mdx(\"p\", null, \"Like this tutorial? Share it\\u2026 Lets make the community grow, I have lots of stuff in store. Stay tuned for the follow-up tutorial. HAPPY CODING!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","excerpt":"Introduction We all know how awesome swiftUI is, but as great as it is, it's still young and missing some important features. So from time to time you may need to tap into the great UIKit framework to use some of its amazing features. Likely for us, there's a way of integrating UIKit views and view…"}}}},"pageContext":{"slug":"swiftui-onboarding-screen"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}