{"componentChunkName":"component---src-templates-course-js","path":"/course/zara-replica","result":{"data":{"contentfulCourse":{"title":"Zara replica","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1606736335/Zara/gallery/hyulvdd1l2qz9quyoyq0.jpg","slug":"zara-replica","plan":"FREE","freeLessons":null,"updatedDate":"2 years ago","excerpt":"At WWDC 20, apple said that it is now possible to build an entire app in swift UI... Although this statement is valid for the majority of apps, there are still  features and functionalities that need the power of UIKit to be implemented correctly.","createdDate":"Monday, 30 November 2020","tags":[{"title":"Course"},{"title":"Free"},{"title":"Swift UI"}],"resources":[{"name":"The basics of the redux architecture","url":"https://danielbernal.co/redux-like-architecture-with-swiftui-basics/"},{"name":"Redux-like state container in SwiftUI. Basics.","url":"https://swiftwithmajid.com/2019/09/18/redux-like-state-container-in-swiftui/"},{"name":"Kingfisher repo","url":"https://github.com/onevcat/Kingfisher.git"}],"lessons":[{"createdDate":"2 years ago","createdAt":"2020-12-03T10:24:04.128Z","isNewLayout":null,"previews":null,"slug":"getting-started-and-login-screen","title":"Lesson 1: Getting started & Login screen","updatedDate":null,"downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZAYlLXZBGbC6U6smKfBWaKQKTDVnurjeu6V","type":"zip","id":"dbc62523-35dc-5715-a7ab-26c5b66466f0","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"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 = {};\n\nvar makeShortcode = function makeShortcode(name) {\n  return function MDXDefaultShortcode(props) {\n    console.warn(\"Component \" + name + \" was not imported, exported, or provided by MDXProvider as global scope\");\n    return mdx(\"div\", props);\n  };\n};\n\nvar Video = makeShortcode(\"Video\");\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(\"p\", null, \"This is part one of many in which we are going to create an e-commerce app inspired by the ZARA mobile app...\"), mdx(\"p\", null, \"At WWDC 20, apple said that it is now possible to build an entire app in swift UI... Although this statement is valid for the majority of apps, there are still  features and functionalities that need the power of UIKit to be implemented correctly. In my humble opinion, UIKit is still relevant to this day, and it will not disappear anytime soon. So if you want to be a flexible iOS developer, you will need  to know both frameworks.\"), mdx(\"p\", null, \"In this series, we will use a bit of UIKit to implement a feature that would be hard  to implement with only swift UI.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"p\", null, \"To follow along, you need:\"), 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 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, \"House keeping\"), mdx(\"p\", null, \"Let's first create a bunch of folders and files that we will use throughout this series, and we will keep adding on them as we go.\"), mdx(\"p\", null, \"To follow along, you need download the starter project source code linked above, and when you finish, try to compare with the final project. \"), mdx(\"p\", null, \"Create the following folders in your root folder: \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \", \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Utils\"), \",  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions, Data, Screens, UIKit\")), mdx(\"h2\", null, \"Extensions\"), mdx(\"p\", null, \"Add the following in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+.swift\")), \" \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nextension Color {\\n    static var background = Color(\\\"background\\\")\\n    static var text = Color(\\\"text\\\")\\n}\\n\")), mdx(\"p\", null, \"The above code will work if you've downloaded the initial project code, otherwise you will need to add the 2 colors in the  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \"  folder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"View+.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nextension View {\\n    func thinFont() -> some View {\\n        self.font(.system(size: 20, weight: Font.Weight.thin, design: Font.Design.default))\\n    }\\n\\n    func lightFont() -> some View {\\n        self.font(.system(size: 12, weight: Font.Weight.light, design: Font.Design.default))\\n    }\\n\\n    func boldFont() -> some View {\\n        self.font(.system(size: 15, weight: Font.Weight.bold, design: Font.Design.default))\\n    }\\n\\n    func textColor() -> some View {\\n        self.foregroundColor(.text)\\n    }\\n\\n    func reverseTextColor() -> some View {\\n        self.foregroundColor(.background)\\n    }\\n\\n    func bgColor() -> some View {\\n        self.background(Color.background)\\n    }\\n\\n    func reverseBgColor() -> some View {\\n        self.background(Color.text)\\n    }\\n}\\n\")), mdx(\"p\", null, \"In case you are not familiar with extensions in swift, I will link some resources to help you out. In short, this will allow us to call the above functions as modifiers, because they are modifiers. We might add more files into this folder as we go.\"), mdx(\"h2\", null, \"Buttons\"), mdx(\"p\", null, \"Now, in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add another folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Buttons.\"), \" Next, add the following in that folder. The following are different styles of buttons we will use throughout this series.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CloseButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct CloseButton: View {\\n\\n    var onDismiss = {}\\n\\n    var body: some View {\\n        Button(action: onDismiss, label: {\\n            Image(systemName: \\\"xmark\\\")\\n                .thinFont()\\n                .textColor()\\n        })\\n    }\\n}\\n\\nstruct CloseButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        CloseButton()\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/bfhrbaxyeie6vbfwvea9.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/bfhrbaxyeie6vbfwvea9.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"BorderedButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct BorderedButton: View {\\n    var text: String\\n    var action = {}\\n\\n    var body: some View {\\n        Button(action: action, label: {\\n            Text(text)\\n                .boldFont()\\n                .textColor()\\n                .padding(.horizontal, 30)\\n                .padding(.vertical, 10)\\n                .border(Color.text, width: 1)\\n        })\\n    }\\n}\\n\\nstruct BorderedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BorderedButton(text: \\\"ADD\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/gallkmhgeyf83qqpuovr.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/gallkmhgeyf83qqpuovr.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"IconButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct IconButton: View {\\n    var icon: String\\n    var action = {}\\n\\n    var body: some View {\\n        Button(action: action, label: {\\n            Image(systemName: icon)\\n                .lightFont()\\n                .textColor()\\n        })\\n    }\\n}\\n\\nstruct IconButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        IconButton(icon: \\\"square.and.arrow.up\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/pws42otjzzjcdie1fgke.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/pws42otjzzjcdie1fgke.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FilledButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct FilledButton: View {\\n    var text: String\\n    var action = {}\\n\\n    var body: some View {\\n        Button(action: action, label: {\\n            Text(text)\\n                .boldFont()\\n                .reverseTextColor()\\n                .padding(.horizontal, 30)\\n                .padding(.vertical, 10)\\n\\n        })\\n    }\\n}\\n\\nstruct FilledButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        FilledButton(text: \\\"LOG IN\\\")\\n            .reverseBgColor()\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/z6eimxh0ixqiyj8pzjeh.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008720/Zara/z6eimxh0ixqiyj8pzjeh.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"NakedButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct NakedButton: View {\\n    var text: String\\n    var alignment: Alignment = .center\\n     var action = {}\\n\\n     var body: some View {\\n         Button(action: action, label: {\\n             Text(text)\\n                 .lightFont()\\n                 .textColor()\\n                 .padding(.vertical, 10)\\n                .frame(maxWidth: .infinity, alignment: alignment)\\n         })\\n     }\\n}\\n\\nstruct NakedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        NakedButton(text: \\\"Have you forgotten your password?\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008721/Zara/rcmzjvirjxmyrzjleidq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008721/Zara/rcmzjvirjxmyrzjleidq.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"LargeNakedButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct LargeNakedButton: View {\\n    var text: String\\n     var action = {}\\n\\n     var body: some View {\\n         Button(action: action, label: {\\n             Text(text)\\n                 .font(.system(size: 30, weight: Font.Weight.black, design: Font.Design.default))\\n                 .foregroundColor(.text)\\n                .frame(maxWidth: .infinity, alignment: .leading)\\n         })\\n     }\\n}\\n\\nstruct LargeNakedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LargeNakedButton(text: \\\"MY INFORMATION\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008721/Zara/r9fbkipu5jn2fgbgmmw2.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598008721/Zara/r9fbkipu5jn2fgbgmmw2.png\"\n  }))), mdx(\"h2\", null, \"Login Screen\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/486528956\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens folder,\"), \" add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"LoginScreen.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct LoginScreen: View {\\n\\n    var onDismiss = {}\\n\\n    @State private var email = \\\"\\\"\\n    @State private var password = \\\"\\\"\\n\\n    var body: some View {\\n\\n                // 1\\n        VStack{\\n                        // 2\\n            HStack {\\n                CloseButton(action: onDismiss)\\n                Spacer()\\n            }\\n\\n            Spacer()\\n\\n                        // 3\\n            VStack(spacing: 20) {\\n                VStack {\\n                    TextField(\\\"Email\\\", text: self.$email)\\n                    Divider()\\n                }\\n\\n                VStack {\\n                    SecureField(\\\"Password\\\", text: self.$email)\\n                    Divider()\\n                }\\n\\n                FilledButton(text: \\\"LOG IN\\\", action: onDismiss)\\n                    .frame(maxWidth: .infinity)\\n                    .reverseBgColor()\\n\\n                NakedButton(text: \\\"Have you forgotten your password?\\\")\\n            }\\n            Spacer()\\n\\n                        // 4\\n            VStack{\\n                BorderedButton(text: \\\"CHAT\\\", action: {})\\n                NakedButton(text: \\\"Don't have an account? Register\\\")\\n            }\\n        }.padding()\\n    }\\n}\\n\\nstruct LoginScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LoginScreen()\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"I first a declare a closure that will be called when the onDismiss  the close button or the login button are tapped. I also declare 2 state properties that will hold the email and password.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"I put everything  inside a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" because the UI will flow from top to bottom.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create the horizontal invisible  navbar that will only contain  the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CloseButton\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Spacer\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Spacer\"), \" is used to push the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CloseButton\"), \" to the left edge of the screen.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, We create the form inside a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" again , and surround it with  2 \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Spacer\"), \" to center it vertically. With swiftUI 2.0, there's no need to handle the keyboard as the form will get out of the way when one starts typing.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last,  we create the footer which is a simple  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598010520/Zara/d07bbbqlzsyssjyt1ter.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598010520/Zara/d07bbbqlzsyssjyt1ter.png\"\n  }))), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That's it for this part one. As you may have noticed, this part was mainly preparation and house keeping, stay tuned for second part in which we will dive deeper into \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TabView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Paging\"), \" like the tik tok app. Subscribe to our newsletter if you haven't done so already and share this article.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#house-keeping","title":"House keeping"},{"url":"#extensions","title":"Extensions"},{"url":"#buttons","title":"Buttons"},{"url":"#login-screen","title":"Login Screen"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-12-12T08:23:33.444Z","isNewLayout":null,"previews":null,"slug":"lesson-2-home-screen","title":"Lesson 2: Home screen","updatedDate":"2020-12-11T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZRwD4XZf68MtWJPsv4Q6h4Dbih2mhtw4v7k","type":"zip","id":"ef9d7d3a-c0e0-5b09-82d4-ae94bf987837","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"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 = {};\n\nvar makeShortcode = function makeShortcode(name) {\n  return function MDXDefaultShortcode(props) {\n    console.warn(\"Component \" + name + \" was not imported, exported, or provided by MDXProvider as global scope\");\n    return mdx(\"div\", props);\n  };\n};\n\nvar Video = makeShortcode(\"Video\");\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(\"p\", null, \"Hey guys, this  is lesson 2 of the  ZARA replica mobile app. Check out the previous lesson if you haven't done so already . So, in this one we will  continue from where we left off  . \"), mdx(\"h2\", null, \"Models\"), mdx(\"p\", null, \"Like I said, we will use in memory data for this series. There's a lot of code for the models and data that's why it's unnecessary to paste all of it here. Download the final project to check it out. I will just go over the code briefly, and later on you will understand better what it does..\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Page.swift\"))), mdx(\"p\", null, \"This contains the page model, it will be used to display a single tab on the home screen which is the screen that will be presented to the user after the login screen.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home.swift\"))), mdx(\"p\", null, \"This contains the Home data which is the screen I have just talked about.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Category.swift\"))), mdx(\"p\", null, \"This is an enum containing only 3 cases men, women, kids. This will also be used as ids for our pages.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Product.swift\"))), mdx(\"p\", null, \"This contains everything product related. This is also where we keep all of our products  hard-coded data.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Tab.swift\"))), mdx(\"p\", null, \"This is also an enum containing 5 cases. It will be used with our custom tab bar view.\"), mdx(\"h2\", null, \"Intro to redux\"), mdx(\"p\", null, \"What is redux? If you are familiar with react (yes, the Javascript library), then chances are you are also familiar with redux. Redux is a state management container that help you centralize your application's state and logic which in turn help your app behaves in a consistent and predictable manner. \"), mdx(\"p\", null, \"The reason redux will work well with swiftUI is  state. Views being function of data state in swiftUI, it does make sense to try out redux to help manage that state. This lesson is not mainly focus on redux, so we will move  a bit faster, but if you want to learn more (which I suggest you do), I will link some materials to  help you out.\"), mdx(\"h3\", null, \"State and change to state (Action)\"), mdx(\"p\", null, \"State in redux is the single source of truth, it will be read-only.\"), mdx(\"p\", null, \"The state will be a simple struct containing various data used in different part of the app. The state will only mutate when an action is emitted by views.  Action will be an enum where cases are operations that need to be performed and update the current state.\"), mdx(\"h3\", null, \"Reducer\"), mdx(\"p\", null, \" It is a function that takes an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" and the current \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" as parameters and returns a new State.\"), mdx(\"h2\", null, \"Data\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489927200\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"This is where our data will reside.  Add the following inside the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Data\"), \" folder:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nclass Store: ObservableObject {\\n\\n            struct State {\\n              var products: [Product]\\n                var bookmarked: [Product]\\n                var shoppingCart: [Product]\\n                var isLoggedIn: Bool = false\\n            }\\n\\n            enum Action {\\n                case addProducts(_ products: [Product])\\n                case bookmark(_ product: Product)\\n                case removeFromBookmark(_ index: Int)\\n                case addToCart(_ product: Product)\\n                    case login\\n            }\\n\\n    @Published private(set) var state: State = .init(products: [], bookmarked: [], shoppingCart: [])\\n\\n    func dispatch(_ action: Action)  {\\n        reducer(state: &state, action: action)\\n    }\\n\\n    func reducer(state: inout State, action: Action) {\\n        switch action {\\n        case .bookmark(let product):\\n            state.bookmarked.append(product)\\n        case .addToCart(let product):\\n            state.shoppingCart.append(product)  \\n        case .removeFromBookmark(let index):\\n            state.bookmarked.remove(at: index)\\n        case .addProducts(let products):\\n            state.products = products\\n        case .login:\\n            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { [weak self] in\\n                self?.state.isLoggedIn = true\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"Like I said, our state will only change when a particular action is emitted from our views, so The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" enum describes exactly  the types of action our app will be dispatching. Bear with me, you will see how we use both the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" shortly.\"), mdx(\"p\", null, \"The reducer takes in a state and an action, and based an the action we mutate the state accordingly. For example, when the  dispatched action is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".bookmark\"), \", we will add the bookmarked product  in the bookmarked array. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" keyword before the state type makes it read and write. We also use a dispatch function  which, as its name implies, will send actions to the reducer. It is a simple function, as you can see, that just calls the reducer function with appropriate arguments.\"), mdx(\"h2\", null, \"UIKit\"), mdx(\"p\", null, \"Like I said before, the current state of swiftuI does not handle everything... There are some layouts that still need the power of UIkit to work properly, one such layout is  a vertically scrollable paging view, like tik tok... We will build this kind of layout for the home screen using the UIKit's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UICollectionView\"), \". The collection view will just be a container, and every single view will be made entirely in swift UI.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Note before you watch the video:\"))), mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"In the video, I forgot to add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UICollectionViewDelegateFlowLayout\"), \" protocol to the Coordinator class, so make sure to add it after \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UICollectionViewDelegate\"))), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489928640\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"Add the following inside the UIKit folder:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CollectionView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct CollectionView: UIViewRepresentable {\\n\\n    var controllers: [UIViewController]\\n    @Binding var currentPage: Int\\n    @Binding var selectedIndex: Int?\\n\\n    func makeUIView(context: Context) -> UICollectionView {\\n        let layout = UICollectionViewFlowLayout()\\n        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)\\n        collectionView.backgroundColor = .clear\\n        collectionView.register(CollectionCell.self, forCellWithReuseIdentifier: CollectionCell.reuseId)\\n        collectionView.dataSource = context.coordinator\\n        collectionView.delegate = context.coordinator\\n        collectionView.isPagingEnabled = true\\n        collectionView.alwaysBounceVertical = false\\n        collectionView.bounces = false\\n        collectionView.showsVerticalScrollIndicator = false\\n        layout.minimumLineSpacing = 0\\n        return collectionView\\n    }\\n\\n    func updateUIView(_ collectionView: UICollectionView, context: Context) {\\n\\n    }\\n\\n    func makeCoordinator() -> CollectionView.Coordinator {\\n        Coordinator(self)\\n    }\\n\\n    class Coordinator: NSObject,UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {\\n        var parent: CollectionView\\n\\n        init(_ parent: CollectionView) {\\n            self.parent = parent\\n        }\\n\\n        func numberOfSections(in collectionView: UICollectionView) -> Int {\\n            return 1\\n        }\\n\\n        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {\\n            return  parent.controllers.count\\n        }\\n\\n        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {\\n            if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionCell.reuseId, for: indexPath) as? CollectionCell {\\n                cell.viewController = parent.controllers[indexPath.item]\\n                return cell\\n            }\\n            return UICollectionViewCell()\\n        }\\n\\n        func collectionView(_ collectionView: UICollectionView,\\n                            didEndDisplaying cell: UICollectionViewCell,\\n                            forItemAt indexPath: IndexPath) {\\n            guard let index = collectionView.indexPathsForVisibleItems.first?.item else {\\n                return\\n            }\\n\\n            self.parent.currentPage = index\\n        }\\n\\n        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {\\n            self.parent.selectedIndex = indexPath.item\\n        }\\n\\n        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {\\n            return CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Notice that we are using the old \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UICollectionViewFlawLayout\"), \", this is because we don't need the new complex layout \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UICollectionViewCompositionalLayout\"), \" which would be too much for this simple layout.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We also set the context's coordinator to be the collectionView's \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"delegate\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"dataSource\"), \"  . The Coordinator is created below the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"makeCoordinator\"), \" method. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In order to enable the  paging, we need to set \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isPagingEnabled\"), \" on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UICollectionView\"), \" to true.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the coordinator, we dequeue a reusable cell using its identifier, then set its viewController property to one of the items in the controllers array.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the didEndDisplaying cell, we retrieve the index of the first visible item and set it to the  current page.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the didSelectItemAt, we set the selectedIndex property to the selected indexPath's item.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the  last \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UICollectionViewDelegateFlowLayout\"), \" method, we return the size we wish our cells to be, in this case we want them to fill the entire screen.\")), mdx(\"p\", null, \"You will have an error caused by the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CollectionCell\"), \", so let's create it now.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CollectionCell.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nclass CollectionCell: UICollectionViewCell {\\n    static let reuseId: String = \\\"CollectionCell\\\"\\n\\n        // 1\\n    var viewController: UIViewController? {\\n        didSet {\\n            if let viewController = self.viewController {\\n                setupCell(viewController)\\n            }\\n        }\\n    }\\n\\n    override init(frame: CGRect) {\\n        super.init(frame: frame)\\n        if let viewController = self.viewController {\\n            setupCell(viewController)\\n        }\\n    }\\n\\n    required init?(coder: NSCoder) {\\n        super.init(coder: coder)\\n    }\\n\\n        // 2\\n    private func setupCell(_ viewController: UIViewController){\\n\\n        guard let view = viewController.view else {\\n            return\\n        }\\n\\n        view.translatesAutoresizingMaskIntoConstraints = false\\n        view.backgroundColor = .clear\\n        self.addSubview(view)\\n\\n        NSLayoutConstraint.activate([\\n            view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\\n            view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\\n            view.topAnchor.constraint(equalTo: contentView.topAnchor),\\n            view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),\\n        ])\\n    }\\n\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Notice that the data we are working with here is an object of type UIViewController. You will see why shortly.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"setupCell\"), \" , we retrieve the view from the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UIViewController\"), \" object which we then add to the cell. This view we are retrieving is a SwiftUI View that will be passed to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UIHostingController\"), \" when we initialize it.\")), mdx(\"p\", null, \"And with that, we are done with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UIKit\"), \" in this series. Next step is to create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PageView\"), \".\"), mdx(\"h2\", null, \"Page View\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489930497\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views,\"), \" add  the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PageView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct PageView: View {\\n\\n    var page: Page\\n\\n    var body: some View {\\n\\n        VStack(spacing: 20) {\\n            Text(page.title)\\n                .multilineTextAlignment(.center)\\n                .font(.system(size: 50, weight: Font.Weight.black, design: Font.Design.default))\\n                .textColor()\\n                .frame(width: Sizes.screenWidth * 0.95, alignment: .center)\\n\\n            Text(page.description)\\n                .textColor()\\n                .multilineTextAlignment(.center)\\n                .frame(width: 300, alignment: .center)\\n\\n            Text(\\\"View\\\")\\n                .font(.system(size: 20, weight: Font.Weight.bold, design: Font.Design.default))\\n                .textColor()\\n                .padding(.horizontal, 30)\\n                .padding(.vertical, 10)\\n                .border(Color.text, width: 1)\\n\\n        }.frame(height: Sizes.screenHeight)\\n        .bgColor()\\n        .edgesIgnoringSafeArea(.all)\\n\\n    }\\n}\\n\\nstruct PageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PageView(page: Page.default)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"This is a simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" containing 3 views. This is the view that will be passed in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UIHostingController\"), \" in order to be added to the cell's content.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598095752/Zara/swcdvfiyzt0cmapwhmtk.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598095752/Zara/swcdvfiyzt0cmapwhmtk.png\"\n  }))), mdx(\"p\", null, \"Let's create a new component view that will display the current displayed page index.\"), mdx(\"h2\", null, \"Vertical Page Indicator\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489932973\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"Add the following inside the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"VerticalPageIndicator.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct VerticalPageIndicator: View {\\n    var numberOfPages: Int = 5\\n    @Binding var selectedPageIndex: Int\\n\\n    var body: some View {\\n        VStack {\\n            ForEach(0..<numberOfPages) { i in\\n                if i == selectedPageIndex {\\n                    Circle()\\n                        .strokeBorder(lineWidth: 1.0, antialiased: true)\\n                        .frame(width: 12, height: 12, alignment: .center)\\n                } else {\\n                    Circle().frame(width: 7, height: 7, alignment: .center)\\n                }\\n\\n            }\\n        }.textColor()\\n    }\\n}\\n\\nstruct VerticalPageIndicator_Previews: PreviewProvider {\\n    static var previews: some View {\\n        VerticalPageIndicator(selectedPageIndex: .constant(0))\\n            .preferredColorScheme(.dark)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"In the above code, we loop through from 0 to numberOfPages - 1, then check if the current index is selected and stroke the circle at that index and make it a bit larger, otherwise we keep it as a plain circle.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598096962/Zara/or8ewto3zefhn9hrvgds.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598096962/Zara/or8ewto3zefhn9hrvgds.png\"\n  }))), mdx(\"h2\", null, \"Paging Controller\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489932099\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"Add the following inside the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PagingController.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct PagingController<T: View>: View {\\n\\n    var viewControllers: [UIHostingController<T>] = []\\n    @State var currentPage = 0\\n        @Binding var selectedIndex: Int?\\n\\n    var body: some View {\\n\\n        return ZStack(alignment: .trailing) {\\n            CollectionView(controllers: viewControllers, currentPage: self.$currentPage, selectedIndex: self.$selectedIndex)\\n            VerticalPageIndicator(numberOfPages: viewControllers.count, selectedPageIndex: self.$currentPage)\\n                .padding(.trailing, 10)\\n        }\\n    }\\n}\\n\\nstruct PagingController_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PagingController(viewControllers: Page.data.map({  UIHostingController(rootView: PageView(page: $0) )}))\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"This view is easier to understand. We put the collectionView and the VerticalPageIndicator inside a ZStack with alignment set to trailing to push the Vertical page indicator to the right edge of the screen\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598097916/Zara/sjly1jrn4bg7c0534jlo.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598097916/Zara/sjly1jrn4bg7c0534jlo.png\"\n  }))), mdx(\"p\", null, \"Swift ui paging view page indicator\"), mdx(\"p\", null, \"Before creating the home screen, let first create a component that will be used there:\"), mdx(\"h2\", null, \"Tab Indicator\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Note before you watch the video:\"))), mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"In the video, I have misplaced the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"frame(width: 250)\"), \" modifier that was supposed to be added to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \", and added it to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" block instead. So, In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TabIndicator\"), \" struct, remove the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"frame(width: 250)\"), \" modifier from the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" block, and add it to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \".\")), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489936333\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"TabIndicatorItem.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct TabIndicatorItem: View {\\n\\n    var category: Category\\n    var isActive: Bool\\n\\n    var body: some View {\\n\\n        return VStack(spacing: 0) {\\n            Text(category.toString().uppercased())\\n                .font(.system(size: 18,\\n                              weight: isActive ? Font.Weight.bold : Font.Weight.light,\\n                              design: Font.Design.default))\\n                .frame(maxWidth: .infinity)\\n                .layoutPriority(1)\\n\\n            if isActive {\\n                Rectangle()\\n                    .frame(width: 50 , height: 2, alignment: .center)\\n            }\\n        }.foregroundColor(.text)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598103567/Zara/tnig99kfnnbbwnxsy4gv.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598103567/Zara/tnig99kfnnbbwnxsy4gv.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"TabIndicator.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct TabIndicator: View {\\n    var category: Category\\n\\n    var body: some View {\\n        HStack{\\n            ForEach(0..<Category.allCases.count) { i in\\nTabIndicatorItem(category: Category.allCases[i], isActive: Category.allCases[i] == category )            }\\n        }.frame(width: 250)\\n    }\\n}\\n\\nstruct TabIndicator_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TabIndicator(category: Category.men)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598103129/Zara/w6s5pxmjdtxoyrj7erdu.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598103129/Zara/w6s5pxmjdtxoyrj7erdu.png\"\n  }))), mdx(\"h2\", null, \"Home Screen\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(\"p\", null, \"You can follow along with the video or continue reading.\"), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/489937739\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeScreen.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HomeScreen: View {\\n\\n    @Binding var category: Category?\\n\\n    private let data = Home.data\\n    @State private var selected = Home.data.first!\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n\\n        return ZStack(alignment: .top) {\\n            TabView(selection: self.$selected.id) {\\n                ForEach(data) { item in\\n                    createPageController(item)\\n                                                .frame(height: Sizes.screenHeight)\\n                        .tag(item.id)\\n                        .onTapGesture(count: 1, perform: { category = item.id })\\n                }\\n\\n            }.background(Color.background)\\n            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))\\n            TabIndicator(category: selected.id)\\n                .padding(.top, 50)\\n        }\\n    }\\n\\n    fileprivate func createPageController(_ data: Home) -> PagingController<PageView> {\\n        return PagingController(\\n            viewControllers: data.pages.map({  UIHostingController(rootView: PageView(page: $0) )})\\n        )\\n    }\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeScreen(category: .constant(Category.kids))\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First  we declare our properties. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selected\"), \" state property will be used with the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TabView.\"), \" the store is retrieved from the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"EnvironmentObject\"), \" that will be passed down from the parent view.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The outer container is a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \" with content aligned to top.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TabView\"), \", passing in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selected\"), \" 's id state property which is of type \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Category\"), \" , we will use it to control the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TabIndicator\"), \" . \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ForEach\"), \" block,  we go through each item, creating a page controller and adding a tag to it. The tag must be of the same type as the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selected\"), \"'s id .\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onTapGesture\"), \", we set the category binding property that we will listen to in order to present \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ProductListingScreen\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Setting the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"indexDisplayMode\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".never\"), \" will prevent the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TabView\"), \" to display the built-in page indicator.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"TabIndicator is finally created passing in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selected\"), \"'s id for the category.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598104338/Zara/h4pqipmlva6wbvlsnypq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598104338/Zara/h4pqipmlva6wbvlsnypq.png\"\n  }))), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"This is it for lesson 2.  stay tuned for the third in which we will tackle \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" while the creating the product page. Subscribe if you haven't done so already. Happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#models","title":"Models"},{"url":"#intro-to-redux","title":"Intro to redux"},{"url":"#data","title":"Data"},{"url":"#uikit","title":"UIKit"},{"url":"#page-view","title":"Page View"},{"url":"#vertical-page-indicator","title":"Vertical Page Indicator"},{"url":"#paging-controller","title":"Paging Controller"},{"url":"#tab-indicator","title":"Tab Indicator"},{"url":"#home-screen","title":"Home Screen"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-12-19T06:44:02.026Z","isNewLayout":null,"previews":null,"slug":"lesson-3-product-listing-screen","title":"Lesson 3: Product Listing Screen","updatedDate":"2020-12-18T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZQ0R8XZQL1nGhBdwypF7LOpdMCw5FdlXBR7","type":"zip","id":"cb5e647e-35a1-50b0-a65f-1a3509728835","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"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 = {};\n\nvar makeShortcode = function makeShortcode(name) {\n  return function MDXDefaultShortcode(props) {\n    console.warn(\"Component \" + name + \" was not imported, exported, or provided by MDXProvider as global scope\");\n    return mdx(\"div\", props);\n  };\n};\n\nvar Video = makeShortcode(\"Video\");\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(\"p\", null, \"Hey guys, this is part 3 of the ZARA replica mobile app. Check out the previous lesson if you haven't done so already in order to follow along. So, in this one we will continue from where we left off last week .\"), mdx(\"h2\", null, \"Sizes\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Utils\"), \" folder, add the following if you don't have it already:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Sizes.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nenum Sizes {\\n\\n        static var navBarHeight: CGFloat { 50 }\\n    static var tabBarHeight: CGFloat { UIScreen.main.bounds.width * 0.15 }\\n    static var screenWidth = UIScreen.main.bounds.width\\n    static var screenHeight = UIScreen.main.bounds.height\\n\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"This is a simple  helper struct that will be used in the app. \"), mdx(\"h2\", null, \"Custom TabBar View\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"TabbarView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct TabbarView: View {\\n\\n    @Binding var tab: Tab?\\n\\n    var body: some View {\\n        HStack {\\n            Button(action: {\\n               onTabSelected(.search)\\n            }, label: {\\n                Image(systemName: \\\"magnifyingglass\\\")\\n                    .thinFont()\\n                    .frame(maxWidth: .infinity)\\n            })\\n\\n            Button(action: { onTabSelected(.bookmark) }, label: {\\n                Image(systemName: \\\"bookmark\\\")\\n                    .thinFont()\\n                    .frame(maxWidth: .infinity)\\n            })\\n\\n            Button(action: { onTabSelected(.menu) }, label: {\\n\\n                Text(\\\"MENU\\\").thinFont()\\n                    .frame(maxWidth: .infinity)\\n            })\\n\\n            Button(action: { onTabSelected(.profile) }, label: {\\n                Image(systemName: \\\"person\\\")\\n                    .thinFont()\\n                    .frame(maxWidth: .infinity)\\n            })\\n\\n            Button(action: { onTabSelected(.cart) }, label: {\\n                Image(systemName: \\\"bag\\\")\\n                    .thinFont()\\n                    .overlay(\\n                        Text(\\\"9+\\\").font(.system(size: 7, weight: Font.Weight.light, design: Font.Design.default))\\n                            .padding(.top, 2)\\n                        , alignment: .center)\\n                    .frame(maxWidth: .infinity)\\n            })\\n\\n        }\\n        .frame(maxWidth: .infinity, maxHeight: Sizes.tabBarHeight, alignment: .top)\\n        .textColor()\\n        .bgColor()\\n\\n    }\\n\\n        func onTabSelected(_ tab: Tab)  {\\n        self.tab = tab\\n    }\\n}\\n\\nstruct TabbarView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TabbarView(tab: .constant(.bookmark))\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"The tab bar view is a horizontal stack of buttons, and in each button's action we call a  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onTabSelected\"), \" function to set  the tab binding property.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598168125/Zara/a75arfpmuo00c3u6bfsy.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598168125/Zara/a75arfpmuo00c3u6bfsy.png\"\n  }))), mdx(\"h2\", null, \"Product Item View\"), mdx(\"p\", null, \"Before creating the product listing screen, we first need to create some views that will compose it. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ProductItemView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct ProductItemView: View {\\n    // 1\\n    var product: Product\\n    var showDetail = {}\\n    var bookmark = {}\\n\\n    var body: some View {\\n\\n        VStack {\\n                                // 2\\n                KFImage(URL(string: product.mainImage)!)\\n                    .resizable()\\n                    .scaledToFill()\\n                    .frame(width: (Sizes.screenWidth / 2 - 25), height: 250)\\n                    .clipped()\\n                    .onTapGesture(perform: showDetail)\\n\\n                 // 3           \\n                VStack(alignment: .leading) {\\n                    Text(product.title)\\n                        .lightFont()\\n                        .lineLimit(1)\\n                    // 4\\n                    HStack {\\n                        Text(product.price)\\n                            .lightFont()\\n                        Spacer()\\n                        Button(action: bookmark, label: {\\n                            Image(systemName: \\\"bookmark\\\")\\n                                .lightFont()\\n                                .textColor()\\n                        })\\n                    }\\n                }\\n\\n        }.padding(.horizontal)\\n    }\\n}\\n\\nstruct ProductItemView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ProductItemView(product: Product.men.first!)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first declare our properties. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"showDetail\"), \" closure will be called when a user taps on the product image to present the product detail view. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"bookmark\"), \" closure will be used for bookmarking.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"KFImage\"), \" in a wrapper around the native swiftUI Image that we use to download and cache remote images.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We put the title and the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" into a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" aligned to the left.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" contains 2 views separated by a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Spacer\"), \" view.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182406/Zara/yomtir5hbxdtdv1ome3z.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182406/Zara/yomtir5hbxdtdv1ome3z.png\"\n  }))), mdx(\"h2\", null, \"NavBar View\"), mdx(\"p\", null, \"Add the following in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"NabBarView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct NavBarView: View {\\n\\n    var title: String\\n    var onReturn = {}\\n    var onFilter = {}\\n\\n    var body: some View {\\n        HStack {\\n            Button(action: onReturn, label: {\\n                Image(systemName: \\\"arrow.left\\\")\\n                    .thinFont()\\n            }).frame(width: 40, alignment: .leading)\\n\\n            Text(title)\\n                .font(.system(size: 18, weight: Font.Weight.bold, design: Font.Design.default))\\n                .frame(maxWidth: .infinity, alignment: .center)\\n\\n            Button(action: onFilter, label: {\\n                Text(\\\"FILTERS\\\")\\n                    .lightFont()\\n            })\\n\\n        }.padding(.horizontal)\\n        .padding(.bottom, 10)\\n        .textColor()\\n        .frame(height: Sizes.navBarHeight, alignment: .bottom)\\n        .bgColor()\\n    }\\n}\\n\\nstruct NavBarView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        NavBarView(title: \\\"NEW IN\\\")\\n            .preferredColorScheme(.light)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"The navbar has a title and 2 buttons, left and right. The left one will be used to return or dismiss and it will call the onReturn closure when clicked and the right one  will used to filter and will call the onFilter closure when clicked.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182406/Zara/sq8mluvc47pmv6rdfeuu.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182406/Zara/sq8mluvc47pmv6rdfeuu.png\"\n  }))), mdx(\"h2\", null, \"Product Listing Screen\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/492567889\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ProductListingScreen.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ProductListingScreen: View {\\n\\n    @Binding var category: Category?\\n    // 1\\n    @EnvironmentObject private var store: Store\\n\\n    @State private var selectedProduct: Product?\\n\\n    // 2\\n    private let columns = [\\n        GridItem(.adaptive(minimum: 150), spacing: 20)\\n    ]\\n\\n    var body: some View {\\n\\n       return ZStack(alignment: .top) {\\n            ScrollView {\\n                // 3\\n                createGrid()\\n            }.padding(.top, Sizes.navBarHeight)\\n                .padding(.horizontal, 15)\\n            // 4\\n            createNavBar()\\n\\n        }.onAppear(perform: {\\n            // 5\\n            setProducts()\\n        }).fullScreenCover(item: $selectedProduct, content: {\\n            ProductDetailScreen(product: $0){\\n                selectedProduct = nil\\n                        }\\n        })\\n\\n    }\\n\\n    // 3\\n    fileprivate func createGrid() -> LazyVGrid<ForEach<[Product], String, ProductItemView>> {\\n        LazyVGrid(columns: self.columns, alignment: .center, spacing: 40) {\\n            ForEach(store.state.products) { product in\\n                createProductItemView(product) // 7\\n            }\\n        }\\n    }\\n\\n    // 4\\n    fileprivate func createNavBar() -> NavBarView {\\n        return NavBarView(title: \\\"NEW IN\\\") {\\n            category = nil\\n        } onFilter: { }\\n    }\\n\\n    // 5\\n    fileprivate func setProducts() {\\n        guard let cat = self.category else {\\n            return\\n        }\\n        switch cat {\\n        case .men:\\n            store.dispatch(.addProducts(Product.men))\\n        case .women:\\n            store.dispatch(.addProducts(Product.women))\\n        case .kids:\\n            store.dispatch(.addProducts(Product.kids))\\n        }\\n    }\\n\\n    // 6\\n    fileprivate func createProductItemView(_ product: Product) -> ProductItemView {\\n        return ProductItemView(product: product) {\\n            selectedProduct = product\\n        } bookmark: {\\n            store.dispatch(.bookmark(product))\\n        }\\n    }\\n\\n}\\n\\nstruct ProductListingScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ProductListingScreen(category: .constant(Category.kids)).environmentObject(Store())\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We need the store object to perform operations and retrieve products, so we get it from the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"environmentObject.\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then declare the columns property that will define how the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"LazyVGrid\"), \" layouts its views. Using  adaptive grid items allows us to fit as many items per row as possible with 150 minimum width\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createGrid\"), \" function, we return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"LazyVGrid\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"columns\"), \" will be an array of adaptive GridItems. Adaptive means we fit as many items as possible in a row, but each item must have  a width of no less than 150. Inside the content block, we return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ForEach\"), \" block looping through the products and creating \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ProductItemView\"), \" for each product.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createNavBar\"), \", we return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NavBar\"), \" instance initialized with a hard-coded title, but in a real world app, this would dynamic. In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onReturn\"), \" closure, we set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"category\"), \" to nil. \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onFilter\"), \" will stay empty.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"setProducts\"), \" will be called in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onAppear\"), \" block of the main \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \" container. We then dispatch an  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"addProducts\"), \" action based on the  unwrapped \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"category\"), \"  to loads products.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createProductItemView\"), \" function creates and return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ProductItemView\"), \" which is the single item in the grid, it has 2 closures, the first one handles the selection of a product that will dispatch  a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectProduct\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UIAction\"), \" to the store and trigger the presentation of its detail screen,  and the second one  handles bookmarking that will dispatch  a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"bookmark\"), \" Action to the store.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In body, we add our grid in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Scrollview\"), \", then below it we add the navbar. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"fullScreenCover\"), \"  containing the detail  screen will be presented when \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"store.uiState.selectedProduct\"), \" is not \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"nil\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Remember to add \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".environmentObject(Store())\"), \"  in the preview for it to work .\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182407/Zara/deqd6zgnqjtrlb5dlq8i.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182407/Zara/deqd6zgnqjtrlb5dlq8i.png\"\n  }))), mdx(\"h2\", null, \"ContentView\"), mdx(\"p\", null, \"Now that we have all we need to implement the content view, let's do that.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Video\"))), mdx(Video, {\n    videoUrl: \"https://player.vimeo.com/video/492569523\",\n    mdxType: \"Video\"\n  }), mdx(\"p\", null, \"Add the following in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ContentView.swift.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct ContentView: View {\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var presentScreen = false\\n    @State private var selectedTab: Tab?\\n    @State private var selectedCategory: Category?\\n\\n    var body: some View {\\n\\n        if store.state.isLoggedIn {\\n            createHomeScreen()\\n        } else {\\n            createLoginScreen()\\n        }\\n    }\\n\\n    fileprivate func createLoginScreen() -> some View {\\n        return LoginScreen {\\n            store.dispatch(.login)\\n        }.transition(.move(edge: .bottom))\\n        .animation(.easeOut)\\n    }\\n\\n    fileprivate func createHomeScreen() -> some View {\\n\\n        return ZStack(alignment: .bottom){\\n            HomeScreen(category: $selectedCategory)\\n            TabbarView(tab: $selectedTab)\\n        }\\n        .fullScreenCover(isPresented: $presentScreen, content: {\\n            if selectedCategory != nil{\\n                ProductListingScreen(category: $selectedCategory)\\n            }\\n\\n            if let tab = selectedTab {\\n                createTabScreen(tab)\\n            }\\n        })\\n        .onChange(of: selectedCategory) { category in\\n            presentScreen.toggle()\\n        }.onChange(of: selectedTab) { _ in\\n            presentScreen.toggle()\\n        }\\n\\n    }\\n\\n    @ViewBuilder\\n    private func createTabScreen(_ tab: Tab) -> some View {\\n        switch tab {\\n        case .bookmark:\\n            Text(\\\"Bookmark\\\")\\n        case .cart:\\n            Text(\\\"Shopping bar\\\")\\n        case .search:\\n            Text(\\\"Search\\\")\\n        case .profile:\\n            Text(\\\"Profile\\\")\\n        case .menu:\\n            Text(\\\"Menu\\\")\\n        }\\n    }\\n\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView().environmentObject(Store())\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the home screen which consist of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \" containing the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HomeScreen\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TabbarView\"), \". This is also where we  add the full screen cover modifier that will present product listing screen or other screen based on the selected tab bar button.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"LoginScreen\"), \", and in its \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onDismiss\"), \" closure, we dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"login\"), \" action. The login functionality does not work for real though.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"presentScreen\"), \" method, we present a screen based on a selected tabBar button. We haven't created those screens yet that's why we have returned placeholder \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \" views.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onTabSelected\"), \" will be used to present other screens.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182407/Zara/deqd6zgnqjtrlb5dlq8i.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598182407/Zara/deqd6zgnqjtrlb5dlq8i.png\"\n  }))), mdx(\"h2\", null, \"ZaraReplicaApp\"), mdx(\"p\", null, \"To build and run the app, we need to set the store as an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"environmentObject\"), \". To do this, add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZaraReplicaApp\"), \" struct.\"), mdx(\"p\", null, \"To the top of the struct\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"private var store = Store()\\n\")), mdx(\"p\", null, \"And this modifier on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".environmentObject(store)\\n\")), mdx(\"p\", null, \"Your file should look like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\n@main\\nstruct ClothingUIApp: App {\\n    @StateObject private var store = Store()\\n\\n    var body: some Scene {\\n        WindowGroup {\\n\\n            ContentView().environmentObject(store)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Now build and run the app. Everything should work fine... make sure you run the app on a physical device as it looks much better than on a simulator.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"And this is lesson 3 of this mini course... stay tuned for lesson 4 which in my humble opinion will be more exciting than the last 3. So make sure you've subscribed to not miss when I publish that. Happy coding guys!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#sizes","title":"Sizes"},{"url":"#custom-tabbar-view","title":"Custom TabBar View"},{"url":"#product-item-view","title":"Product Item View"},{"url":"#navbar-view","title":"NavBar View"},{"url":"#product-listing-screen","title":"Product Listing Screen"},{"url":"#contentview","title":"ContentView"},{"url":"#zarareplicaapp","title":"ZaraReplicaApp"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-01-11T05:54:46.260Z","isNewLayout":null,"previews":null,"slug":"lesson-4-product-detail-screen","title":"Lesson 4: Product Detail Screen","updatedDate":"2021-01-09T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZeh8YXZhIE8750HmaVe5FadjrA7v5Q9hLv7","type":"zip","id":"a3ffd5b6-7e4b-55ab-ac89-5e5c8db1172f","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"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(\"p\", null, \"Hey guys, this part 4 of the ongoing series e-commerce series mobile app. Check out the previous lesson if you haven't done so already in order to follow along. In the previous lesson, we created the home screen and product listing view with other views that compliment them.  In this one we will continue from where we left off. Let's get started\"), mdx(\"p\", null, \"Let's start with the product detail screen. The view consists of an images carousel scrolled vertically like the one on the home screen and a sheet that contains further details which is expendable. There's also a way of seeing the each image fullscreen where one can double-click it to zoom and drag it around. let's implement one of them at a time.\"), mdx(\"h2\", null, \"Custom sheet\"), mdx(\"p\", null, \"We will create the  sheet in such a way that it can be reused even if we won't reuse it in this series. In the Views folder, add the following :\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CustomSheet.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct CustomSheet<Content: View>: View {\\n\\n    // 1\\n    enum SheetState {\\n        case expanded, folded\\n    }\\n\\n    @State private var yOffset: CGFloat = Sizes.screenHeight\\n    @State private var sheetState: SheetState = .folded\\n\\n    // 2\\n    @Binding var progress: CGFloat\\n\\n    // 3\\n    var foldedHeight: CGFloat\\n    var expandedHeight: CGFloat\\n\\n    // 4\\n    var content: (() -> Content)\\n\\n    init(progress: Binding<CGFloat>,foldedHeight: CGFloat = 300, expandedHeight: CGFloat = Sizes.screenHeight, @ViewBuilder content: @escaping (() -> Content)) {\\n        self._progress = progress\\n        self.content = content\\n        self.foldedHeight = foldedHeight\\n        self.expandedHeight = expandedHeight\\n    }\\n\\n    var body: some View {\\n        Group {\\n            createContent()\\n        }.frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)\\n        .onAppear(perform: {\\n            yOffset = deltaHeight()\\n        })\\n    }\\n\\n    fileprivate func createContent() -> some View {\\n        Color.background\\n            .overlay(content(), alignment: .top)\\n            .frame(height: expandedHeight, alignment: .bottom)\\n            .offset(x: 0, y: yOffset)\\n            .gesture(\\n                DragGesture().onChanged({ drag in\\n                    handleOnDrag(drag)\\n                }).onEnded({ drag in\\n                    handleOnEnded(drag)\\n                })\\n            ).animation(.easeInOut, value: self.sheetState)\\n    }\\n\\n    // 6\\n    private func handleOnDrag(_ drag: DragGesture.Value) {\\n        self.yOffset = sheetState == .expanded\\n            ? max(drag.translation.height, 0)\\n            : min(drag.location.y, deltaHeight() )\\n        self.progress = yOffset / deltaHeight()\\n    }\\n\\n    // 7\\n    private func handleOnEnded(_ drag: _ChangedGesture<DragGesture>.Value) {\\n        switch sheetState {\\n        case .expanded:\\n            resetValues(for: drag.translation.height > foldedHeight  ? .folded : .expanded)\\n        case .folded:\\n            resetValues(for: abs(drag.location.y) < deltaHeight()  ? .expanded : .folded)\\n        }\\n    }\\n\\n    private func resetValues(for state: SheetState)  {\\n        self.yOffset = state == .folded ? deltaHeight() : 0\\n        self.sheetState = state\\n        self.progress = state == .folded ? 1 : 0\\n    }\\n\\n    private func deltaHeight() -> CGFloat {\\n        expandedHeight - foldedHeight\\n    }\\n}\\n\\nstruct CustomSheet_Previews: PreviewProvider {\\n    static var previews: some View {\\n        CustomSheet(progress: .constant(0), foldedHeight: 200, expandedHeight: UIScreen.main.bounds.height - 200) {\\n            Text(\\\"This is a custom sheet\\\")\\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\\n                .background(Color.blue)\\n                .edgesIgnoringSafeArea(.all)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create an enum that will hold the sheet state. The other 2 private properties do the  following: \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"yOffset\"), \" is the one that will change the state of the view being dragged and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"sheetState\"), \" will keep track of the current sheet state.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"progress\"), \", as its name implies, will track the drag progress from 0 to 1.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We expose the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"foldedHeight\"), \" and  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"expandedHeight\"), \"  to allow the calling site to set them via the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"init\"), \" function.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"content\"), \" is a closure that takes in nothing and returns a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"View\"), \" which can be any type of view. This will allow us to pass in a complex view like the built-in swift ui containers.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"we add the content as an overlay to the  color that will act as a background, then align the overlay's content  to the top. There's another away of doing this... One could use a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \" container instead of what we have done.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then add a gesture and handle it in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleOnDrag\"), \" function at little down (here)... In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleDrag\"), \" we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"yOffset\"), \" based on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"sheetState\"), \". So when the sheet is in the expanded state, we set the offset to be translate height value which is the difference between the start y location and the current y location, if it's the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"foldedState\"), \", we just use the location of the finger.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleOnEnded\"), \"  function, we reset the 3 values \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"yOffset\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"sheetState\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"progress\"), \" not only based on the sheet state but also based on the current location of the drag.\")), mdx(\"h2\", null, \"Image Page View\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct ImageView: View {\\n\\n    var image: String\\n    private let screenWidth = Sizes.screenWidth\\n    private let screenHeight = Sizes.screenHeight\\n\\n    var body: some View {\\n        URL(string: image).map {\\n\\n            KFImage($0)\\n                .resizable()\\n                .aspectRatio(contentMode: .fill)\\n                .frame(width: screenWidth, height: screenHeight)\\n        }\\n    }\\n}\\n\\nstruct ImageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ImageView(image: \\\"zara-textured-check-shirt 3.JPG\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"First import KingFisherSwiftUI to help download images. Then we declare some more properties at top.  Inside the body, we create and unwrap the url using map, and inside the map block we create the Image using KFImage and the $0 (which represent the unwrapped image).  We then set the aspect ration to fill and the frame to the screen size. We will use this view in the section.\"), mdx(\"h2\", null, \"Product ImageView\"), mdx(\"p\", null, \" As I mentioned earlier, the user will have the ability to view a single image, so let's create the view that will handle that. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ProductImageView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct ProductImageView: View {\\n\\n    var imgUrl: String\\n    // 1\\n    var animation: Namespace.ID\\n    var onDismissPreview = {}\\n\\n    @State private var zoomLevel: CGFloat = 1\\n    @State private var offset: CGSize = .zero\\n\\n    var body: some View {\\n        ImageView(imgUrl: imgUrl)\\n            .scaleEffect(self.zoomLevel)\\n            .offset(self.offset)\\n            .gesture(handleDragGesture())\\n            .gesture(handleMagnificationGesture())\\n            .onTapGesture(count: 2, perform: handleDoubleTapGesture)\\n            .onTapGesture(count: 1, perform: handleSingleTapGesture)\\n            .background(Color.background)\\n            .edgesIgnoringSafeArea(.all)\\n            .animation(.easeIn, value: self.zoomLevel)\\n    }\\n\\n    private func handleDragGesture() -> _EndedGesture<_ChangedGesture<DragGesture>> {\\n        return DragGesture()\\n            .onChanged({ value in\\n                if zoomLevel > 1 {\\n                    self.offset = value.translation\\n                }\\n            }).onEnded({ value in\\n                withAnimation {\\n                    self.offset = .zero\\n                }\\n            })\\n    }\\n\\n     private func handleMagnificationGesture() -> _EndedGesture<_ChangedGesture<MagnificationGesture>> {\\n        return MagnificationGesture().onChanged({ (value) in\\n            self.zoomLevel = value\\n        }).onEnded { value in\\n            if value < CGFloat(1) {\\n                self.zoomLevel = 1\\n            }\\n        }\\n    }\\n\\n    private func handleSingleTapGesture() {\\n        if zoomLevel == 1 {\\n            onDismissPreview()\\n        }\\n    }\\n\\n    private func handleDoubleTapGesture() {\\n        if zoomLevel > 1 {\\n            zoomLevel = 1\\n        } else {\\n            zoomLevel = 2\\n        }\\n    }\\n}\\n\\nstruct ProductImageView_Previews: PreviewProvider {\\n    @Namespace static var animation\\n    static var previews: some View {\\n        ProductImageView(imgUrl: \\\"\\\", animation: animation)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Make sure you import the  KingfisherSwiftUI framework first.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we declare the first 3 that will be passed in via  the initialiser. We declare a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"[Namespace.ID](http://namespace.ID)\"), \" to synchronise animations between this particular view and the selected \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ImageView\"), \"  in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ProductDetailView\"), \" ,that we haven't  created yet, using \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"matchedGeometryEffect\"), \". \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next we declare the states properties that will update the view every time  they change. The first one is the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"zoomLevel\"), \" to change how zoomed in the image is and the second is \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"offset\"), \" for moving the image around.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then attach a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"scaleEffect\"), \" modifier to  scale up or down the image when a user performs a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"MagnificationGesture\"), \"  that is handled in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleMagnificationGesture\"), \" function. The maximum zoom amount will be 2x the normal size of the image.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"offset\"), \" is used to move around the image using a drag gesture when in zoomed-in state .The drag gesture will only work when the image is zoomed in.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then add 2 \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onTapGesture\"), \" modifiers, one handles a double taps and another single tap. The double tap is used to zoom in or out the image whereas the single tap will be used to dismiss the preview.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleDragGesture\"), \", we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"offset\"), \" with the translation of the finger in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onChange\"), \" block, and in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onEnded\"), \" block, we reset the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"offset\"), \" to zero which is the initial position.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleMagnificationGesture\"), \", we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"zoomLevel\"), \" with the value in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onChange\"), \" block, then when the user lifts the finger, we reset the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"zoomLevel\"), \" if the value is less than 1 which is the zoomed-out state.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleSingleTapGesture\"), \",  we first check if we are in the normal state, and if so we dismiss the image preview.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In this \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"handleDoubleTapGesture\"), \", we first check if we are zoomed-in ( if \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"zoomLevel\"), \" is greater than 1), we reset it to 1 which is the normal state. If we are not in zoomed-in, we set the zoomLevel to 2 to zoom the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ImageView\"), \" in.\")), mdx(\"h2\", null, \"Product detail content\"), mdx(\"p\", null, \"This is the content that will go in the custom sheet we've created, so in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ProductDetailContent.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ProductDetailContent: View {\\n\\n    var product: Product\\n\\n    var body: some View {\\n        VStack(alignment: .leading, spacing: 30) {\\n            VStack(alignment: .leading, spacing: 10) {\\n                Text(product.title)\\n                    .font(.system(size: 15, weight: Font.Weight.bold, design: Font.Design.default))\\n\\n                Text(product.price)\\n                    .font(.system(size: 14, weight: Font.Weight.light, design: Font.Design.default))\\n\\n                HStack {\\n                    BorderedButton(text: \\\"ADD\\\") {}\\n                    Spacer()\\n                    HStack(spacing: 20) {\\n                        IconButton(icon: \\\"square.and.arrow.up\\\") {}\\n                        IconButton(icon: \\\"bookmark\\\") {}\\n                        IconButton(icon: \\\"bag\\\") {}\\n                    }\\n                }\\n            }\\n\\n            Text(product.description).font(.system(size: 14, weight: Font.Weight.light, design: Font.Design.default))\\n\\n            VStack(alignment: .leading, spacing: 20) {\\n                ArrowButtton(text: \\\"COMPOSITION AND CARE\\\") {}\\n                ArrowButtton(text: \\\"IN-STORE AVAILABILITY\\\") {}\\n                ArrowButtton(text: \\\"SHIPPING AND RETURNS\\\") {}\\n                ArrowButtton(text: \\\"CAN WE HELP YOU\\\") {}\\n            }\\n\\n        }.padding(.all, 20)\\n    }\\n}\\n\\nstruct ProductDetailContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ProductDetailContent(product: Product.kids.first!)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"The above code is way simpler than it looks... Everything will flow vertically, just take a look a preview and read the code, it should be self-explanatory.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598266772/Zara/sprbgxptwlaunpkwywh1.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598266772/Zara/sprbgxptwlaunpkwywh1.png\"\n  }))), mdx(\"h2\", null, \"Clear NavBar\"), mdx(\"p\", null, \"In the Views folder, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ClearNavBar.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct ClearNavBar: View {\\n\\n    var opacity: Double\\n    var onDismiss = {}\\n\\n    var body: some View {\\n        HStack {\\n\\n            Button(action: onDismiss, label: {\\n                Image(systemName: \\\"xmark\\\")\\n                    .font(.system(size: 25, weight: Font.Weight.light, design: Font.Design.default))\\n                    .foregroundColor(.gray)\\n                    .padding(10)\\n            })\\n\\n            Spacer()\\n        }\\n                .padding(.horizontal, 20)\\n        .frame(height: UIScreen.main.bounds.width * 0.22, alignment: .bottom)\\n        .background(Color.background.opacity(opacity))\\n        .edgesIgnoringSafeArea(.top)\\n\\n    }\\n}\\n\\nstruct ClearNavBar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ClearNavBar(opacity: 1)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"This navigation bar is a bit special, notice how we expose the opacity, we want the opacity to progressively increase to 1 or decrease to 0 as the user drags the sheet up or down.\"), mdx(\"h2\", null, \"Product Detail Screen\"), mdx(\"p\", null, \"This is where we will put everything product detail related views we've just created. \"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens\"), \" folder, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct ProductDetailScreen: View {\\n\\n    var product: Product\\n    var onDismiss = {}\\n\\n    @State private var progress: CGFloat = 1\\n    @State private var selectedIndex: Int?\\n\\n    @Namespace private var animation\\n\\n    private let foldedHeight: CGFloat = 150\\n    private let expandedHeight = Sizes.screenHeight - UIScreen.main.bounds.width * 0.22\\n\\n    var body: some View {\\n\\n        ZStack(alignment: .top) {\\n            createPagingController()\\n            createNavBarView()\\n\\n            if selectedIndex == nil {\\n                createCustomSheet()\\n            } else {\\n                createProductImageView()\\n            }\\n        }.edgesIgnoringSafeArea(.all)\\n        .animation(.linear, value: selectedIndex)\\n\\n    }\\n\\n    fileprivate func createCustomSheet() -> some View {\\n        CustomSheet(progress: self.$progress, foldedHeight: foldedHeight, expandedHeight: expandedHeight) {\\n            ProductDetailContent(product: product)\\n        }.transition(.move(edge: .bottom))\\n    }\\n\\n    fileprivate func createProductImageView() -> ProductImageView {\\n        let images = product.images\\n        return ProductImageView(imgUrl: images[selectedIndex!], animation: animation)\\n            { self.selectedIndex = nil }\\n    }\\n\\n    fileprivate func createPagingController() -> some View {\\n        PagingController(viewControllers:\\n                                    product.images.map({\\n                                                        UIHostingController(rootView:\\n                                                                                ImageView(imgUrl: $0)\\n                                                                                .matchedGeometryEffect(id: $0, in: animation)\\n                                                        )}), selectedIndex: self.$selectedIndex)\\n    }\\n\\n    fileprivate func createNavBarView() -> some View {\\n        ClearNavBar(opacity: Double(1 - progress), onDismiss: onDismiss)\\n            .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)\\n    }\\n\\n}\\n\\nstruct DetailScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ProductDetailScreen(product: Product.men.first!)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We will need the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"product\"), \", so we declare it. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onDIsmiss\"), \" will called when the close button on the clear nav bar is tapped. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we declare the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"progress\"), \" to keep track of the drag progress, and the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedIndex\"), \" to show the product image when selected.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The animation property will be used sync the transition between the image displayed in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CollectionCell\"), \" and the single full screen productImage. The next 2 properties will hold the folded and expanded height  of the detail sheet.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \", we add the various views that make up the product detail screen. The part to pay attention to is the condition, so if selectedIndex is nil meaning  if no cell is selected, we will show the custom sheet, otherwise we will show the product Image view.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createCustomSheet\"), \" function creates and returns a custom sheet instance, adding a move transition to it so that it animated from the bottom.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createProductImageView\"), \", we create and return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ProductItemView\"), \" instance. When the close button is tapped, the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onDIsmiss\"), \" closure will run and inside it we set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedIndex\"), \" to nil.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createPagingController\"), \", we do the same thing we did when creating the paging controller in the Home screen view, but in this one, we looping over an array of url strings rather pages, then we bind \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedIndex\"), \" property to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedIndex\"), \" in the CollectionView.\")), mdx(\"p\", null, \"The preview will not display the images unless you click the play button on it.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598270359/Zara/mq2q59bhdllufqvsywal.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1598270359/Zara/mq2q59bhdllufqvsywal.png\"\n  }))), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"And this is lesson 4 of this mini course... stay tuned for more. I will start releasing the PREMIUM Course starting next week. Cheers!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#custom-sheet","title":"Custom sheet"},{"url":"#image-page-view","title":"Image Page View"},{"url":"#product-imageview","title":"Product ImageView"},{"url":"#product-detail-content","title":"Product detail content"},{"url":"#clear-navbar","title":"Clear NavBar"},{"url":"#product-detail-screen","title":"Product Detail Screen"},{"url":"#conclusion","title":"Conclusion"}]}}}}]}},"pageContext":{"slug":"zara-replica"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}