{"componentChunkName":"component---src-templates-course-js","path":"/course/movie-booking-app","result":{"data":{"contentfulCourse":{"title":"Movie Booking App","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1590177000/MovieTicketApp/ebnqtqvseqlyzdubroos.jpg","slug":"movie-booking-app","plan":"FREE","freeLessons":null,"updatedDate":"3 years ago","excerpt":"I work as a full-time software engineer, and in my spare time, I like to try out new technologies and share them with the world because I believe it’s the best way to learn and improve my own skills as well as help other people... ","createdDate":"Wednesday, 25 December 2019","tags":[{"title":"Free"},{"title":"Swift UI"},{"title":"Course"}],"resources":[{"name":"Lazy variables","url":"https://www.hackingwithswift.com/example-code/language/what-are-lazy-variables"},{"name":"Compositional layout ","url":"https://developer.apple.com/videos/play/wwdc2019/215/"},{"name":"The movie database","url":"https://www.themoviedb.org/account/signup"},{"name":"Kingfisher repo","url":"https://github.com/onevcat/Kingfisher.git"}],"lessons":[{"createdDate":"3 years ago","createdAt":"2020-08-15T17:33:56.467Z","isNewLayout":null,"previews":null,"slug":"swiftui-movie-booking-app-part-2","title":"Movie & Actor Detail View","updatedDate":"2020-05-09T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZADkBkZTTSUoMbdt2jdeX8vLxupj8AIlfy0","type":"zip","id":"39164a39-d003-586d-bf49-59ddd2a5fd73","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, \"Last week, I published the first part of this article\\u2026 if you haven\\u2019t checked it out, \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.liquidcoder.com/swift-ui-movie-booking-app-part-1/\",\n    \"title\": \"Swift UI - Movie booking app part 1\"\n  }), \"here it is\"), \". In this second part, we will work on the movie and actor detail view. So, without further ado, let\\u2019s get started.\"), mdx(\"p\", null, \"You will need the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.liquidcoder.com/swift-ui-movie-booking-app-part-1/\",\n    \"title\": \"Swift ui - finished first partt\"\n  }), \"finished first part\\xA0\"), \"to follow along, and you can get the source code by clicking the floating button located on the bottom-left corner.\"), mdx(\"h2\", null, \"Single Movie View\"), mdx(\"p\", null, \"In your Xcode root project, create a folder named  Screens containing a swift UI file that you\\u2019ll name \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SingleMovieView.swift\"), \".  Screens will contain our main views which in turn will be a combination of smaller parts that we will call views.   Of course\"), mdx(\"p\", null, \"We want to use the same view for all our Movie types which are  popular, trending and upcoming. This is easily done because all our movies conform to the Movie protocol, so we will just need to make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SingleMovieView\"), \" generic and it should be able to used by all the movie types. \"), mdx(\"p\", null, \"Modify the definition of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SingleMovieView\"), \" struct with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct SingleMovieView<T: Movie>: View {}\\n\")), mdx(\"p\", null, \"As you can see, our generic parameter must conform to the Movie protocol.\\nAt the top of the struct add this property  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"var movie: T\"), \". This is the movie that we will be working with.\"), mdx(\"p\", null, \"Add this method below below the body block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createPosterImage() -> some View {\\n           return Image(uiImage: UIImage(named: \\\"\\\\(movie.image).jpg\\\") ?? UIImage() ).resizable()\\n           .aspectRatio(contentMode: .fit)\\n       }\\n\")), mdx(\"p\", null, \"This method just renders the poster image for the movie. You will have a compiler error in the preview. To fix it , replace the preview content with the following code: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    MovieDetailView<Popular>(movie: Popular.default)\\n\\n\")), mdx(\"p\", null, \"And add the following code inside body:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"ScrollView(showsIndicators: false) {\\n                    VStack(alignment: .leading) {\\n                        createPosterImage()\\n                    }\\n                }.edgesIgnoringSafeArea(.top)\\n\")), mdx(\"p\", null, \"This code is pretty self-explanatory. We make everything scrollable because we don\\u2019t know how tall the VStack will be. You will not be able to see what we have just done for now, but here is a preview:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578121019/movie%20store/vvcyvdderctjsrqxpytf.png\",\n    \"alt\": \"Swift ui - Movie booking app\"\n  }))), mdx(\"h2\", null, \"Movie detail view\"), mdx(\"p\", null, \"Let\\u2019s now work on the remaining details. Create a Views folder, and inside it, add a swift ui file named  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieDetailView.swift\"), \". This will also be a generic view, so change its definition to this: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct MovieDetailView<T: Movie>: View \\n\")), mdx(\"p\", null, \"And add the following property to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var movie: T\\n\")), mdx(\"p\", null, \"Then, below the body block, add the following method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" fileprivate func createTitle() -> some View {\\n        return Text(self.movie.title)\\n        .font(.system(size: 35, weight: .black, design: .rounded))\\n        .layoutPriority(1)\\n        .multilineTextAlignment(.leading)\\n        .lineLimit(nil)\\n    }\\n\")), mdx(\"p\", null, \"The bit to note here is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"layoutPriority\"), \".  Setting it to 1 for the  title text will make it write and fill the available space rather than be cut off. The default value is 0. Here is apple\\u2019s description of it:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Sets the priority by which a parent layout should apportion\\nspace to this child.\"), mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \" The default priority is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"0\"), \".  In a group of sibling views,\\nraising a view's layout priority encourages that view to shrink\\nlater when the group is shrunk and stretch sooner when the group\\nis stretched.\"), mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \" A parent layout should offer the child(ren) with the highest\\nlayout priority all the space offered to the parent minus the\\nminimum space required for all its lower-priority children, and\\nso on for each lower priority value.\")), mdx(\"p\", null, \"Then below that method, add this one:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" fileprivate func createGenreList() -> some View {\\n        return ScrollView(.horizontal) {\\n            HStack{\\n                ForEach(self.movie.genres, id: \\\\.self){ genre in\\n                    Text(genre)\\n                        .bold()\\n                        .padding(5)\\n                        .background(Color.lightGray)\\n                        .cornerRadius(10)\\n                        .foregroundColor(Color.gray)\\n                    \\n                }\\n            }\\n        }\\n    }\\u2028\\n\")), mdx(\"p\", null, \"This code just creates an horizontally scrolled list of genres styled properly.\\nThen add this below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createDescription() -> some View {\\n        return Text(self.movie.description).lineLimit(nil).font(.body)\\n    }\\n\")), mdx(\"p\", null, \"Notice in the above code , we set the line limit to nil, doing so means there\\u2019s not linelimit. So the text is free to use as many lines as required.\"), mdx(\"p\", null, \"And last add this last method below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" fileprivate func createChooseSeatButton() -> some View {\\n        return LCButton(text: \\\"Choose seats\\\") {}\\n    }\\n\")), mdx(\"p\", null, \"The above code will get you an error because you haven\\u2019t created the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LCButton\"), \".  Open the finished project, and drag the LCButton.swift in your Views folder. The code is pretty simple to understand.\"), mdx(\"p\", null, \"Before, we add everything in the body, we will need a view called \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LineRatingView\"), \" that will render a line graph indicating a movie\\u2019s rating. So let\\u2019s create that.\"), mdx(\"h2\", null, \"Line Rating View\"), mdx(\"p\", null, \"In the Views folder, add the a swift ui file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LineRatingView.swift\"), \", and inside it, replace everything with this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct LineRatingView: View {\\n     var height: CGFloat = 10\\n    var value: Double = 0.0\\n    \\n    fileprivate func createPath(with size: CGSize) -> some View {\\n        return Rectangle()\\n            .frame(width: size.width, height: self.height, alignment: .leading).cornerRadius(self.height / 2)\\n            .foregroundColor(Color.gray.opacity(0.5))\\n    }\\n    \\n    fileprivate func createProgress(with size: CGSize) -> some View {\\n        return Rectangle()\\n            .fill(LinearGradient(gradient: Gradient(colors: [Color.accent, .darkPurple]) , startPoint: .leading, endPoint: .trailing))\\n            .animation(.easeIn(duration: 1))\\n            .frame(width: size.width * CGFloat(self.value) / 5, height: self.height, alignment: .leading)\\n            .cornerRadius(self.height / 2)\\n    }\\n    \\n    var body: some View {\\n        HStack {\\n            GeometryReader{ gr in\\n                ZStack(alignment: .leading) {\\n                    self.createPath(with: gr.size)\\n                    self.createProgress(with: gr.size)\\n                }\\n            }.frame(height: 15)\\n            \\n            Text(\\\"\\\\(String(format: \\\"%.1f\\\", self.value))/5\\\")\\n                .bold()\\n                .foregroundColor(Color.darkPurple)\\n        }\\n    }\\n}\\n\\nstruct LineRatingView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LineRatingView()\\n    }\\n}\\n\")), mdx(\"p\", null, \"The above code does the following:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first method create the path that has a gray background color and  takes the entire width.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The second method creates the progress indicator with a beautiful gradient color. This will be animatable reflecting the rating value.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then put everything inside a ZStack with the alignment set to leading for the progress to start on the left rather than the center.\\nHere is a preview of what we\\u2019ve just built:  \")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578124828/movie%20store/ernlk5ucu6hnagu5ahro.png\",\n    \"alt\": \"Swift ui - Progress view\"\n  }))), mdx(\"p\", null, \"Now, open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieDetailView.swift\"), \", and replace everything in the body\\nblock with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" VStack(alignment: .leading) {\\n            createTitle()\\n            LineRatingView(value: movie.rating)\\n            createGenreList()\\n            \\n            HStack {\\n                 Text(self.movie.releaseDate).foregroundColor(Color.gray)\\n                Spacer()\\n                Text(self.movie.runtime).foregroundColor(Color.gray)\\n            }.padding(.vertical)\\n            \\n            createDescription()\\n            createChooseSeatButton()\\n            \\n}.padding(.horizontal).padding(.bottom, 20)\\n\")), mdx(\"p\", null, \"Go back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SingleMovieView\"), \" struct, and add the following below  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPosterImage()\"), \" call inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nMovieDetailView(movie: self.movie)\\n\\n\")), mdx(\"p\", null, \"Let\\u2019s now wire everything in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieStoreApp\"), \".  Open the the MovieStoreApp.swift file, and put the following at the top: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State private var showDetails = false\\n    @State private var selectedIndex: Int?\\n    @State private var section: HomeSection = .Popular\\n    @State private var showSheet = false\\n\")), mdx(\"p\", null, \"Next, add these in the body block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var popular: [Popular] = []\\n        var trending: [Trending] = []\\n        var actors: [Actor]  = []\\n        var upcoming: [Upcoming] = []\\n        \\n        switch section {\\n        case .Popular:\\n            popular = model.allItems[section] as! [Popular]\\n        case .Actors:\\n            actors = model.allItems[section] as! [Actor]\\n        case .Trending:\\n            trending = model.allItems[section] as! [Trending]\\n        case .Upcoming:\\n            upcoming = model.allItems[section] as! [Upcoming]\\n        }\\n\")), mdx(\"p\", null, \"Then in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createCollectionView()\"), \" method, inside didSelectItem closure, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" self.selectedIndex = indexPath.item\\n                self.section = HomeSection.allCases[indexPath.section]\\n                self.showSheet.toggle()\\n\")), mdx(\"p\", null, \"Next, in the body block, add the following modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createCollectionView()\"), \" method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".sheet(isPresented: self.$showSheet) {\\n                if self.selectedIndex == nil{\\n                   Text(\\\"To be implemented\\\")\\n                } else {\\n                    if self.section == HomeSection.Trending {\\n                        SingleMovieView(movie: trending[self.selectedIndex!])\\n                    }\\n                    if self.section == HomeSection.Popular {\\n                        SingleMovieView(movie: popular[self.selectedIndex!])\\n                        \\n                    }\\n                    if self.section == HomeSection.Upcoming {\\n                        SingleMovieView(movie: upcoming[self.selectedIndex!])\\n                    }\\n                    if self.section == HomeSection.Actors {\\n                    }\\n                }\\n            }\\n\")), mdx(\"p\", null, \"If the selectedIndex is nil, then the seeAll button was clicked which means we will show the vertically scrolled list of movies or actors, otherwise we show the singleMovieView passing in the respective type of movie.\\nRun the app and click any movie, you should see the SingleMovieView presented. Let\\u2019s now create the actor detail view now.\"), mdx(\"h2\", null, \"Actor Detail View\"), mdx(\"p\", null, \"In the Screens folder, add a swift ui file named ActorDetailView.swift, and put the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ActorDetailView: View {\\n    \\n    var actor: Actor\\n        \\n    var body: some View {\\n        ScrollView(showsIndicators: false) {\\n            VStack(alignment: .leading) {\\n                Image(uiImage: UIImage(named: actor.image) ?? UIImage() )\\n                        .resizable()\\n                        .aspectRatio(contentMode: .fit)\\n                Text(actor.name)\\n                        .font(.system(size: 35, weight: .black, design: .rounded))\\n                        .padding(.horizontal)\\n                Text(actor.bio)\\n                    .font(.body)\\n                    .padding()\\n            }\\n        }.edgesIgnoringSafeArea(.top)\\n    }\\n}\\n\\nstruct ActorDetailView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ActorDetailView(actor:  Actor.default)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Pretty simple, right?  We\\u2019ve just stacked 3 views vertically in a scrollView.\\nNext, add the following code in the sheet content block where the section is equal \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeSection.Actors\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"ActorDetailView(actor: actors[self.selectedIndex!])\\n\")), mdx(\"p\", null, \"With that in place, you should now be able  to see the actor\\u2019s detail when you click any of the actors.\"), mdx(\"h2\", null, \"Movie List View\"), mdx(\"p\", null, \"In the Screens folder, add a new swift ui file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieListView.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct MovieListView<T: Movie>: View {\\n    \\n    var movies: [T]\\n    var section: HomeSection\\n    \\n    var body: some View {\\n        NavigationView {\\n            List(0..<movies.count) { i in\\n                    MovieListRow<T>(movie: self.movies[i])\\n            }.navigationBarTitle(section.rawValue)\\n        }\\n    }\\n}\\n\\nstruct MovieListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MovieListView<Popular>(movies: [], section: .Trending)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We also want the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieListView\"), \" to be generic in order to display all 3 types of movies. Your code will not  compile because you are missing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieListRow\"), \",  let\\u2019s create that now.\"), mdx(\"h2\", null, \"Movie List Row\"), mdx(\"p\", null, \"In the Views folder, create a swift ui file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieListRow.swift\"), \", and replace everything with the following :\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nstruct MovieListRow<T: Movie>: View {\\n    var movie: T\\n    \\n    fileprivate func createImage() -> some View {\\n        return Image(uiImage: UIImage(named: \\\"\\\\(movie.image).jpg\\\") ?? UIImage() )\\n        .resizable()\\n        .aspectRatio(contentMode: .fit).cornerRadius(20)\\n    }\\n        \\n    fileprivate func createTitle() -> some View {\\n        return Text(movie.title)\\n        .font(.system(size: 25, weight: .black, design: .rounded))\\n        .foregroundColor(Color.white)\\n    }\\n    \\n    var body: some View {\\n        \\n       return ZStack(alignment: .bottom) {\\n        createImage()\\n        \\n        VStack(alignment: .leading) {\\n                createTitle()\\n                LineRatingView(value: movie.rating)\\n            }.frame(maxWidth: .infinity, alignment: .leading)\\n                .padding()\\n                .background(LinearGradient(gradient: Gradient(colors: [Color.black, Color.clear]) , startPoint: .bottom , endPoint: .top)).cornerRadius(20)\\n                .shadow(radius: 10)\\n        \\n        }.padding(.vertical)\\n    }\\n}\\n\\n\\n\")), mdx(\"p\", null, \"The above code is self-explanatory, and here is a preview:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578556678/movie%20store/ilnkvlb2cl65f7bqbob8.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieStoreApp\"), \", add the following code in the sheet condition where the selectedIndex is nil.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if self.section == HomeSection.Trending {\\n                            MovieListView<Trending>(movies: trending, section: .Trending)\\n                        }\\n                        \\n                        if self.section == HomeSection.Popular {\\n                            MovieListView<Popular>(movies: popular, section: .Popular)\\n                            \\n                        }\\n                        if self.section == HomeSection.Upcoming {\\n                            MovieListView<Upcoming>(movies: upcoming, section: .Upcoming)\\n                        }\\n                        if self.section == HomeSection.Actors {\\n                            ActorListView(actors: actors, section: .Actors)\\n                        }\\u2028\\n\")), mdx(\"p\", null, \"This code will run when one clicks on the seeAll button, and depending on the section, a list view of movies or actors will be presented. You will have a compiler error caused by the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ActorListView\"), \", it\\u2019s almost similar to the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieListView\"), \", so I won\\u2019t explain it again, but you can just copy the files from the finished project into yours. Before you run the app, add the following code in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seeAllForSection\"), \" closure:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" self.section = section\\n self.showSheet.toggle()\\n self.selectedIndex = nil\\n\")), mdx(\"p\", null, \"And with that in place, you can now run the app and click the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" button to see the ListView in action.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"After publishing the first part,  I got an amazing response from you guys which showed me that you like this kind of tutorials, and I really do enjoy making them, so I will make more of them for as long as I can. Subscribe and share this article. Happy coding!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#single-movie-view","title":"Single Movie View"},{"url":"#movie-detail-view","title":"Movie detail view"},{"url":"#line-rating-view","title":"Line Rating View"},{"url":"#actor-detail-view","title":"Actor Detail View"},{"url":"#movie-list-view","title":"Movie List View"},{"url":"#movie-list-row","title":"Movie List Row"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T17:36:36.914Z","isNewLayout":null,"previews":null,"slug":"swiftui-movie-booking-app-part-3","title":"Movie Theatre Seats View","updatedDate":"2020-01-17T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZkT0WkZ5lDpk8fmtHza8TaincEu6Hd2NnXy","type":"zip","id":"1cf2fdc7-631e-5c52-827f-168b21d64e1b","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, \"Last week, we built the detail views for both movies and actors, if you haven\\u2019t read it, I\\u2019d suggest that read it  \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.liquidcoder.com/swiftui-movie-booking-app-part-2/\",\n    \"title\": \"swift ui - detail views\"\n  }), \"here\"), \" before reading this. In this tutorial, we will create the movie theatre where one will be able to choose a  seat, date and time. Like all the other articles from this series, you will receive an email containing the source code if you are subscribed, and if you are not, you can get the code \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"#\"\n  }), \"here\"), \". Without further ado, let\\u2019s jump right into it.\"), mdx(\"h2\", null, \"Intro\"), mdx(\"p\", null, \"The source code folder will contain the starter project, part 1, part 2 and part 3, so you can just pick up from part 2, and start following along. However, if you were following along from part 1, just continue from where you  we left off last week.\"), mdx(\"h2\", null, \"TheatreView\"), mdx(\"p\", null, \"We will create this layout from top to bottom hence starting with the TheatreView. In your Views folder, add a file named  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TheatreView.swift\"), \", and inside it replace everything with the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct TheatreView: View {\\n\\n    @Binding var selectedSeats:[Seat]\\n    \\n    var body: some View {\\n        ZStack {\\n            Rectangle()\\n                .fill(LinearGradient(gradient: Gradient(colors: [Color.darkPurple.opacity(0.3), .clear]) , startPoint: .init(x: 0.5, y: 0.0), endPoint: .init(x: 0.5, y: 0.5)) )\\n                .frame(height: 420)\\n                .clipShape(ScreenShape(isClip: true))\\n                .cornerRadius(20)\\n            \\n            ScreenShape()\\n                .stroke(style:  StrokeStyle(lineWidth: 5,  lineCap: .square ))\\n                .frame(height: 420)\\n                .foregroundColor(Color.accent)\\n\\n        }\\n    }\\n}\\n\\nstruct TheatreView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TheatreView(selectedSeats: .constant([]))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"You will get a bunch of errors because you are missing a couple of items. Let\\u2019s start fixing them now. \"), mdx(\"h2\", null, \"Seat Model\"), mdx(\"p\", null, \"In the Model folder, add the following code inside a swift file called \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Seat.swift\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct Seat: Identifiable {\\n    var id: UUID\\n    var row: Int\\n    var number: Int\\n    \\n    static var `default`: Seat { Seat(id: UUID(), row: 0, number: 0) }\\n}\\n\")), mdx(\"p\", null, \"That fixes one error, let\\u2019s move to the next one.\"), mdx(\"h2\", null, \"Screen shape\"), mdx(\"p\", null, \"In order to create a curved line like you saw in the video, we will need to make our own custom shape, and fear not, swift ui makes the process real easy, you will just need to understand some basic geometry, and by basic I mean really basic. So create a folder named Shapes, and inside it add a swift file named ScreenShape containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\n\\nstruct ScreenShape: Shape {\\n\\n    var screenCurveture: CGFloat = 30\\n    var isClip = false\\n    \\n    func path(in rect: CGRect) -> Path {\\n        \\n        return Path{ path in\\n            path.move(to: CGPoint(x: rect.origin.x + screenCurveture, y: rect.origin.y +  screenCurveture))\\n            path.addQuadCurve(to: CGPoint(x: rect.width - screenCurveture, y: rect.origin.y + screenCurveture), control: CGPoint(x: rect.midX, y: rect.origin.y) )\\n            if isClip{\\n                path.addLine(to: CGPoint(x: rect.width, y: rect.height))\\n                path.addLine(to: CGPoint(x: rect.origin.x, y: rect.height))\\n                path.closeSubpath()\\n            }\\n        }\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code draws a curved line from left to right, and depending on the isClip flag, we will draw the bottom shape  which looks like a reversed bucket to  which we will be adding the gradient that will mimic a screen light reflection. With that in place, all of the errors will be fixed, just build the project if it doesn\\u2019t happen automagically.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578728126/movie%20store/hlblh22gsdctsmxwczd6.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Let\\u2019s move on to creating the SeatView now, but first let\\u2019s create the chairView.\"), mdx(\"h2\", null, \"ChairView\"), mdx(\"p\", null, \"In your Views folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ChairView.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\n\\nstruct ChairView: View {\\n    \\n    var width: CGFloat = 50\\n    var accentColor: Color = .blue\\n    var seat = Seat.default\\n    @State var isSelected = false\\n    var isSelectable = true\\n    var onSelect: ((Seat)->()) = {_ in }\\n    var onDeselect: ((Seat)->()) = {_ in }\\n    \\n    \\n    var body: some View {\\n       Text(\\\"Hello world!\\\")\\n    }\\n}\\n\\nstruct ChairView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ChairView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The properties inside the struct are self-explanatory. If you don\\u2019t understand some of them, don\\u2019t worry you will understand after using them shortly. \"), mdx(\"p\", null, \"Then replace the Text inside body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" VStack(spacing: 2) {\\n            Rectangle()\\n                .frame(width: self.width, height: self.width * 2/3)\\n                .foregroundColor(isSelectable ? isSelected ? accentColor : Color.gray.opacity(0.5) : accentColor)\\n            .cornerRadius(width / 5)\\n            \\n            Rectangle()\\n                .frame(width: width - 10, height: width / 5)\\n                .foregroundColor(isSelectable ? isSelected ? accentColor :  Color.gray.opacity(0.5) : accentColor)\\n                .cornerRadius(width / 5)\\n        }\\n\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578729776/movie%20store/dn6rkk7jwqaaaynvfjoj.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"This is just 2 vertically stacked rectangles, nothing fancy. And as you may have noticed, we used some of the properties we declared above. Let\\u2019s now add a tap gesture to handle selection and deselection. Add the following modifier to the above VStack container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".onTapGesture {\\n            if self.isSelectable{\\n                self.isSelected.toggle()\\n                if self.isSelected{\\n                    self.onSelect(self.seat)\\n                } else {\\n                    self.onDeselect(self.seat)\\n                }\\n            }\\n        }\\n\\n\")), mdx(\"p\", null, \"As you can see, we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onSelect\"), \" closure when \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSelected\"), \" is true passing in the current seat, otherwise we call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onDeselect\"), \".\\nLet\\u2019s now put everything together inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TheatreView\"), \" view. \"), mdx(\"p\", null, \"Now open the Theatre struct, and below body, add the following method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createFrontRows() -> some View {\\n        \\n        let rows: Int = 2\\n        let numbersPerRow: Int = 7\\n        \\n        return\\n            \\n            VStack {\\n                ForEach(0..<rows, id: \\\\.self) { row in\\n                    HStack{\\n                        ForEach(0..<numbersPerRow, id: \\\\.self){ number in\\n                            ChairView(width: 30, accentColor: .accent, seat: Seat(id: UUID(), row: row + 1, number: number + 1) , onSelect: { seat in\\n                                self.selectedSeats.append(seat)\\n                            }, onDeselect: { seat in\\n                                self.selectedSeats.removeAll(where: {$0.id == seat.id})\\n                            })\\n                        }\\n                    }\\n                }\\n        }\\n    }\\n    \\n\")), mdx(\"p\", null, \"Here is what the above code does:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The 2 properties above mean we want the first 2 rows to have 7 seats\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"To create a grid in swift ui, we need nest an HStack inside a VStack or vice-versa, which is becomes very inefficient for a larger amount  of data, so I wouldn\\u2019t use this as a UICollectionView. However, for our use-case, it won\\u2019t be an issue, we are good.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We implement the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onSelect\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onDeselect\"), \"  by adding and removing  the tapped chair respectively.\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578815013/movie%20store/m6u2f0ewczlttgonhkzq.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Next, let\\u2019s create the remaining chairs. So add the following method below the one you\\u2019ve just added:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createBackRows() -> some View {\\n        \\n        \\n        let rows: Int = 5\\n        let numbersPerRow: Int = 9\\n        \\n        return\\n            \\n            VStack {\\n                ForEach(0..<rows, id: \\\\.self) { row in\\n                    HStack{\\n                        ForEach(0..<numbersPerRow, id: \\\\.self){ number in\\n                            ChairView(width: 30, accentColor: .accent, seat: Seat(id: UUID(), row: row + 3, number: number + 15) , onSelect: { seat in\\n                                self.selectedSeats.append(seat)\\n                            }, onDeselect: { seat in\\n                                self.selectedSeats.removeAll(where: {$0.number == seat.number})\\n                            })\\n                        }\\n                    }\\n                }\\n        }\\n    }\\u2028\\n\")), mdx(\"p\", null, \"This code is exactly the same as the one I have just explained above. The only exception is that we add 3 to rows and 15 to columns each time we create a new Seat to account for the front rows that have already been created.\"), mdx(\"p\", null, \"Next, let\\u2019s create the seat legend. Add the following code below the \\u201CcreateBackRows\\u201D:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createSeatsLegend() -> some View{\\n        HStack{\\n            ChairLegend(text: \\\"Selected\\\", color: .accent)\\n            ChairLegend(text: \\\"Reserved\\\", color: .clearPurple)\\n            ChairLegend(text: \\\"Available\\\", color: .gray)\\n        }.padding(.horizontal, 20).padding(.top)\\n    }\\n\")), mdx(\"h2\", null, \"Chair Legend\"), mdx(\"p\", null, \"The above code will not compile, so create a swift ui file in Views named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ChairLegend.swift\"), \", and replace the content of the file with the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct ChairLegend: View {\\n    var text = \\\"Selected\\\"\\n    var color: Color = .gray\\n    \\n    var body: some View {\\n        HStack{\\n             ChairView(width: 20,accentColor: color, isSelectable: false)\\n            Text(text).font(.subheadline).foregroundColor(color)\\n        }.frame(maxWidth: .infinity)\\n    }\\n}\\n\\n\\nstruct ChairLegend_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ChairLegend()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Now the errors should be gone by now, if not, just build your project (CMD + B).\"), mdx(\"p\", null, \"Then add the following code below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScreenShape()\"), \" inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TheaterView\"), \" :\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       \\n                 VStack {\\n                     createFrontRows()\\n                     createBackRows()\\n                     createSeatsLegend()\\n                 }\\n\")), mdx(\"p\", null, \"Here is a preview:  \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578815013/movie%20store/pdlrilt2vwzvilucevqw.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"The next step will be to create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeatChoice\"), \" Screen. This is the main view that will put everything together.\"), mdx(\"h2\", null, \"Seats Choice Screen\"), mdx(\"p\", null, \"In the Screens folder, add a swift ui file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SeatsChoiceView.swift\"), \". And inside it replace the content with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct SeatsChoiceView<T: Movie>: View {\\n    var movie: T\\n        \\n    @State private var selectedSeats: [Seat] = []\\n    @State private var showBasket: Bool = false\\n    @State private var date: TicketDate = TicketDate.default\\n    @State private var hour: String = \\\"\\\"\\n    @State private var showPopup = false\\n\\n var body: some View { \\n    Text(\\\"Hello world!\\\")\\n}\\n\\n}\\n\")), mdx(\"p\", null, \"As you can see, the struct is generic to be used for any type of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Movie\"), \". You will get an error caused by the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TicketDate\"), \" model.\"), mdx(\"h2\", null, \"Ticket Date\"), mdx(\"p\", null, \"In the Models folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TicketDate.swift\"), \", and put the following inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct TicketDate: Equatable {\\n    var day: String\\n    var month: String\\n    var year: String\\n    \\n    static var `default`: TicketDate { TicketDate(day: \\\"\\\", month: \\\"\\\", year: \\\"\\\") }\\n}\\u2028\\n\")), mdx(\"p\", null, \"Build (CMD + B) the project if the error does not go away automatically. Go back to the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SeatsChoice\"), \" file, and inside the struct\\u2019s body, replace the Text  view with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" NavigationView {\\n            ScrollView(showsIndicators: false ) {\\n                VStack(spacing: 0) {\\n                    TheatreView(selectedSeats: self.$selectedSeats).padding(.top, 20)\\n\\n                }.navigationBarTitle(\\\"Choose seats\\\", displayMode: .large)\\n                    .frame(maxHeight: .infinity)\\n                    .accentColor(Color.accent)\\n            }\\n        }\\n\")), mdx(\"p\", null, \"Right now, the preview is showing the exact screen as the one shown in the TheatreView . Let\\u2019s now create the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DateTimeView\"), \".\"), mdx(\"p\", null, \"In the Views folder, create a \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DateTimeView.swift\"), \" file, and at the top, add  the following code :\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State private var selectedDate: TicketDate = TicketDate.default\\n    @State private var selectedHourndex: Int = -1\\n    private let dates = Date.getFollowingThirtyDays()\\n    \\n    @Binding var date: TicketDate\\n    @Binding var hour: String\\n\")), mdx(\"p\", null, \"You will need to create  a Date extension to silence the error you have just got, so in the Extensions folder, create a \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DateExt\"), \" file containing the following code lines of code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\n\\nextension Date{\\n    \\n     static var thisYear: Int {\\n        let formatter = DateFormatter()\\n        formatter.dateFormat = \\\"yyyy\\\"\\n        let component = formatter.string(from: Date())\\n        \\n        if let value = Int(component) {\\n            return value\\n        }\\n        return 0\\n    }\\n    \\n    private static func getComponent(date: Date, format: String) -> String {\\n           let formatter = DateFormatter()\\n           formatter.dateFormat = format\\n        formatter.locale = Locale.autoupdatingCurrent\\n           let component = formatter.string(from: date)\\n           return component\\n    }\\n    \\n    static func getFollowingThirtyDays(for month: Int = 1) -> [TicketDate]{\\n        var dates = [TicketDate]()\\n        let dateComponents = DateComponents(year: thisYear , month: month)\\n        let calendar = Calendar.current\\n        let date = calendar.date(from: dateComponents)!\\n\\n        let range = calendar.range(of: .day, in: .month, for: date)!\\n        \\n        for i in range{\\n            guard let fullDate = calendar.date(byAdding: DateComponents(day: i) , to: Date()) else { continue }\\n            let d = getComponent(date: fullDate, format: \\\"dd\\\")\\n            let m = getComponent(date: fullDate, format: \\\"MM\\\")\\n            let y = getComponent(date: fullDate, format: \\\"yy\\\")\\n            let ticketDate = TicketDate(day: d, month: m, year: y)\\n            dates.append(ticketDate)\\n        }\\n        \\n        return dates\\n        \\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"That is a big chunk of code, so let\\u2019s break it down. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"thisYear\"), \":  is a property that returns the current year converted to an integer.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"getComponent\"), \" : Accept a date and a format then returns a formatted date based on the passed in format.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"getFollowingThirtyDays\"), \" : As its name implies, this method generates the next 30, 31 or 28 days depending on the month, and returns an array of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TicketDate\"), \" created from the generated dates.\\nBefore we continue implementing the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"DateTimeView\"), \", we will need 2 more views,  Date and Time Views.  \")), mdx(\"p\", null, \"In your Views folder, add the a DateView.swift file with the following code in it:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct DateView: View {\\n    var date: TicketDate = TicketDate(day: \\\"03\\\", month: \\\"11\\\", year: \\\"20\\\")\\n    var isSelected: Bool\\n    var onSelect: ((TicketDate)->()) = {_ in }\\n    \\n    \\n    var body: some View {\\n        VStack {\\n            Text(\\\"\\\\(date.day)\\\")\\n                .font(.title)\\n                .bold()\\n                .foregroundColor(isSelected ? .white : .textColor)\\n            \\n            Text(\\\"\\\\(date.month)/\\\\(date.year)\\\")\\n                .foregroundColor(isSelected ? .white : .textColor)\\n                .font(.callout)\\n                .padding(.top, 10)\\n            \\n        }.padding()\\n            .background( isSelected ? Color.accent: Color.gray.opacity(0.3))\\n            .clipShape(DateShape())\\n            .cornerRadius(10)\\n            .onTapGesture {\\n                self.onSelect(self.date)\\n        }\\n    }\\n}\\n\\nstruct DateView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        DateView(isSelected: false )\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code is relatively simple, it just stacks 2 texts vertically. Your code will not compile caused by the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DateShape()\"), \" which you are about to create. \"), mdx(\"h2\", null, \"Date Shape\"), mdx(\"p\", null, \"In the Shapes folder, add a \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DateShape.swift\"), \" file with the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct DateShape: Shape {\\n\\n    var cutRadius: CGFloat = 5\\n    \\n    func path(in rect: CGRect) -> Path {\\n        \\n        return Path{ path in\\n            path.move(to: CGPoint(x: rect.origin.x, y: rect.origin.y ))\\n            path.addLine(to: CGPoint(x: rect.width, y: rect.origin.y))\\n            path.addLine(to: CGPoint(x: rect.width, y: rect.height - rect.height / 4))\\n            path.addArc(center: CGPoint(x: rect.width, y: rect.height - rect.height / 4 + cutRadius), radius: cutRadius, startAngle: Angle(degrees: -90) , endAngle: Angle(degrees: 90) , clockwise: true)\\n            path.addLine(to: CGPoint(x: rect.width, y: rect.height))\\n            path.addLine(to: CGPoint(x: rect.origin.x, y: rect.height))\\n            path.addLine(to: CGPoint(x: rect.origin.x, y: rect.height - rect.height / 4 + cutRadius * 2))\\n            path.addArc(center: CGPoint(x: rect.origin.x, y: rect.height - rect.height / 4 + cutRadius), radius: cutRadius, startAngle: Angle(degrees: 90) , endAngle: Angle(degrees: -90) , clockwise: true)\\n            path.closeSubpath()\\n            \\n        }\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code creates a shapes with 2 cut-outs on each side. Build (Command + B) your project, and the errors should now leave you alone.\\nHere is the preview:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578949922/movie%20store/qgw4z7z5hacvsqmnklib.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Next, let\\u2019s create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TimeView\"), \" which will be way simpler than the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DateView\"), \". \"), mdx(\"h2\", null, \"Time View\"), mdx(\"p\", null, \"In the Views folder , create a TimeView.swift file, and paste the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct TimeView: View {\\n    var index: Int\\n    var isSelected: Bool\\n    var onSelect: ((Int)->()) = {_ in }\\n\\n\\n    var body: some View {\\n        Text(\\\"\\\\(index):00\\\")\\n            .foregroundColor(isSelected ? .white : .textColor)\\n            .padding()\\n            .background( isSelected ? Color.accent : Color.gray.opacity(0.3))\\n            .cornerRadius(10).onTapGesture {\\n                self.onSelect(self.index)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"And voil\\xE0, that\\u2019s our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TimeView\"), \". Pretty simple!\"), mdx(\"p\", null, \"We are now ready to finish the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DateTimeView\"), \". Add the following 2 methods below the body block in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DateTimeView\"), \" struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createDateView() -> some View{\\n\\n        VStack(alignment: .leading) {\\n            Text(\\\"Date\\\")\\n            .font(.headline).padding(.leading)\\n             ScrollView(.horizontal, showsIndicators: false) {\\n                HStack{\\n                    ForEach(dates, id: \\\\.day){ date in\\n                        DateView(date: date, isSelected: self.selectedDate.day == date.day, onSelect: { selectedDate in\\n                            self.selectedDate = selectedDate\\n                            self.date = selectedDate\\n                        })\\n                    }\\n                }.padding(.horizontal)\\n            }\\n        }\\n    }\\n    \\n    fileprivate func createTimeView() -> some View {\\n        VStack(alignment: .leading) {\\n            Text(\\\"Time\\\").font(.headline).padding(.leading)\\n\\n            ScrollView(.horizontal, showsIndicators: false) {\\n                HStack{\\n                    ForEach(0..<24, id: \\\\.self){ i in\\n                        TimeView(index: i, isSelected: self.selectedHourndex == i, onSelect: { selectedIndex in\\n                            self.selectedHourndex = selectedIndex\\n                            self.hour = \\\"\\\\(selectedIndex):00\\\"\\n                        })\\n                    }\\n                }.padding(.horizontal)\\n            }\\n        }\\n    }\\n    \\n\")), mdx(\"p\", null, \"Those 2 methods create a scrollable list of dates and times respectively, and  handle the selection of each item using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onSelect\"), \" closure. Next replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text(\\u201CHello world!\\u201D)\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack(alignment: .leading, spacing: 30) {\\n            createDateView()\\n            createTimeView()\\n        }\\n\")), mdx(\"p\", null, \"Here what the finished \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DateTimeView\"), \" looks like:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578951505/movie%20store/eu7auftyb2ofxpvc9res.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now, open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeatsChoiceScreen\"), \", and add the following lines of code below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TheatreView\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"DateTimeView(date: self.$date, hour: self.$hour)\\nLCButton(text: \\\"Continue\\\", action: {}).padding()\\n\")), mdx(\"p\", null, \"And the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeatsChoice\"), \" view now looks like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1578952095/movie%20store/kpd8unas6pwd2ytgbcmb.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Let\\u2019s now link the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SingleMovieView\"), \"  with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeatsChoiceView\"), \".  Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DetailView.swift\"), \" file, add this at the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State private var showSeats: Bool = false\\n\")), mdx(\"p\", null, \"Then find the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createChooseSeatButton\"), \" method, add this inside the button closure:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"            self.showSeats.toggle()\\n\")), mdx(\"p\", null, \"And last, add the following modifier to the same button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" .sheet(isPresented: self.$showSeats) {\\n            SeatsChoiceView(movie: self.movie)\\n        }.padding(.vertical)\\n\")), mdx(\"p\", null, \"Now run the app, and navigate to the screen we\\u2019ve just created. Try switching to dark mode. If your app does not work as expected, get the finished project, and compare with your code.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That\\u2019s it for this part folks, stay tuned for the next. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"QUICK SIDE-NOTE : Based on this series\\u2019 reception, I will definitely make more of these, so buckle up cause it\\u2019s going to be a bumpy, but exciting ride. Make sure you\\u2019re subscribed if you\\u2019re not already, and please share this article everywhere. HAPPY CODING!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#intro","title":"Intro"},{"url":"#theatreview","title":"TheatreView"},{"url":"#seat-model","title":"Seat Model"},{"url":"#screen-shape","title":"Screen shape"},{"url":"#chairview","title":"ChairView"},{"url":"#chair-legend","title":"Chair Legend"},{"url":"#seats-choice-screen","title":"Seats Choice Screen"},{"url":"#ticket-date","title":"Ticket Date"},{"url":"#date-shape","title":"Date Shape"},{"url":"#time-view","title":"Time View"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T17:38:17.542Z","isNewLayout":null,"previews":null,"slug":"movie-booking-app-part-4","title":"Movie Tickets & Payment Form","updatedDate":"2020-01-26T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZWfE2kZUgydk4ydLyypp3SBOEwHR0SxTr6X","type":"zip","id":"5e7078e3-40d0-585e-8a2a-d4af9f195cd9","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, \"This is the fourth and last part of this series. This series should\\u2019ve taken more than four parts had we used real data from a remote API and utilised real payment process and validations.  As you may have noticed, we used a bit of UIKit to achieve some functionalities that would\\u2019ve been a pain in the neck if we had stuck with swift UI. I think with its first version, swift UI achieved more than what we could hope for, and I am more than certain that subsequent versions will be amazing. \"), mdx(\"h2\", null, \"Intro\"), mdx(\"p\", null, \"Like the other tutorials, if you are subscribed, check your email inbox for the source, otherwise click here to download the source code. The folder will contain the starter project, part 1 to part 4. You can just pick up from part 3, and start following along. However, if you were following along from part 1, just continue from where you left off last week.\"), mdx(\"h2\", null, \"Basic Validation\"), mdx(\"p\", null, \"In the previous tutorial, we finished the SeatsChoice screen. The next step is to show the Basket View after a user has chosen the seats, date and time. We also won\\u2019t allow going to the next screen if at least one of those requirements is not met.  So let\\u2019s start with the inputs validations in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeatsChoiceView\"), \". So add the following method below body:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func validateInputs() -> Bool {\\n        self.selectedSeats.count > 0\\n        && self.date != TicketDate.default\\n        && !self.hour.isEmpty\\n    }\\n\")), mdx(\"p\", null, \"Then add the following line of code in the Continue button action:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.showBasket = self.validateInputs()\\n\")), mdx(\"p\", null, \"We perform some basic validations to check whether the requirements are met. We then present the basket view if everything is okay, otherwise, we show a popup View telling the user that he has missed something.\"), mdx(\"p\", null, \"Now, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"padding()\"), \" modifier on the button with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".sheet(isPresented: self.$showBasket) {\\n                       Text(\\\"To be implemented!\\\")\\n                    }.padding()\\n\\n\")), mdx(\"p\", null, \"We will replace the text shortly. First , let\\u2019s create the popup View\"), mdx(\"h2\", null, \"Popup View\"), mdx(\"p\", null, \"The main job of this pop-up view will be to display validation errors, and after that, we will move on to creating the Basket View. So add the following method below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createPopupContent() -> some View {\\n        VStack {\\n            Text(\\\"Not allowed\\\").font(.system(size: 20, weight: Font.Weight.semibold))\\n            Text(\\\"You need to select at least one seat, a date and hour in order to continue.\\\")\\n                .multilineTextAlignment(.center).frame(maxHeight: .infinity)\\n            LCButton(text: \\\"Ok\\\") {\\n                withAnimation {\\n                    self.showPopup.toggle()\\n                }\\n            }\\n        }.frame(width: UIScreen.main.bounds.width * 0.8, height: 200, alignment: .bottom)\\n            .padding()\\n            .background(Color.background.opacity(0.7))\\n            .cornerRadius(20)\\n            .shadow(color: Color.textColor.opacity(0.3), radius: 20, x: 0, y: 10)\\n            .transition(.move(edge: .bottom))\\n    }\\n\")), mdx(\"p\", null, \"The above code creates the content that will be displayed in the popup when there are errors. Of course, in a production app, you should do proper validations. For simplicity, we won\\u2019t go deeper into validations and error handling.  In brief, the above code does the following:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Stacks 2 \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text()\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Button()\"), \" vertically\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Styles the view to create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Card\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Applies a transition to animate (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"move\"), \") the pop-up from the bottom to  the center of the parent view because that\\u2019s where the initial position is.\")), mdx(\"p\", null, \"Then add this modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"navigationView\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".blur(radius: self.showPopup ? 10 : 0).overlay(\\n            VStack{\\n                if self.showPopup {\\n                    self.createPopupContent()\\n                } else {\\n                    EmptyView()\\n                }\\n            }.frame(maxWidth: .infinity, maxHeight: .infinity)\\n                .background( self.showPopup ? Color.background.opacity(0.3) : .clear)\\n        )\\n\")), mdx(\"p\", null, \"The most important thing to understand here is the overlay. It layers a secondary view in front of the view you add it to. According to apple, when you apply an overlay to a view, the original view continues to provide the layout characteristics for the resulting view naming the width, height etc...\"), mdx(\"p\", null, \"Now add the following  code in the continue button\\u2019s action closure:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"withAnimation {\\n    self.showPopup = !self.validateInputs()\\n}\\n\")), mdx(\"p\", null, \"If you click the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"continue button\"), \" without selecting some of the 3 requirements, you will see the validation view pops up.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1579554970/movie%20store/u8utkkzl1kvlqhpslivb.gif\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now let\\u2019s create the Basket View.\"), mdx(\"h2\", null, \"Basket View\"), mdx(\"p\", null, \"Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text(\\\"To be implemented!\\\")\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".sheet\"), \" closure with the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" BasketView(ticket: Ticket(id: UUID(), movie: self.movie, date: self.date, hour: self.hour) , selectedSeats: self.selectedSeats)\\n\")), mdx(\"p\", null, \"BasketView does not exist yet, that\\u2019s why you are getting those errors. I will not explain the code that creates the horizontally scrollable list of tickets in this article because I have already written an amazing tutorial about it that you can read \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"#\"\n  }), \"here\"), \". I will just paste the code here to guide you through creating corresponding files. If you want, you can drag and drop the files in the finished project and skip to the PaymentView.swift altogether.\"), mdx(\"p\", null, \"Let\\u2019s create that now. In the Screens folder, add a BasketView.swift  file with the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct BasketView<T: Movie>: View {\\n    var ticket: Ticket<T>\\n    var selectedSeats: [Seat]\\n    @State private var showPaymentScreen = false\\n    \\n    \\n    var body: some View {\\n        \\n        return\\n            VStack(spacing: 0) {\\n                ScrollView(.horizontal, showsIndicators: false) {\\n                    HStack {\\n                        ForEach(selectedSeats) { seat in\\n                            GeometryReader { gr in\\n                                self.renderTicket(Ticket(id: self.ticket.id, movie:  self.ticket.movie, date:  self.ticket.date, hour:  self.ticket.hour), seat: seat , angle: gr.frame(in: .global).minX / -10)\\n                            }.frame(width: UIScreen.main.bounds.width)\\n                        }\\n                    }\\n                }\\n                LCButton(text: \\\"Buy\\\", action: {self.showPaymentScreen.toggle()})\\n                    .padding(.horizontal)\\n                    .padding(.bottom)\\n        }\\n    }\\n    \\n    func renderTicket(_ ticket: Ticket<T>, seat: Seat, angle: CGFloat) -> some View {\\n        \\n        return TicketView(ticket: ticket, seat: seat)\\n            .rotation3DEffect(Angle(degrees: Double(angle)) , axis: (x: 0, y: 10.0, z: 0))\\n    }\\n}\\n\\nstruct BasketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BasketView(ticket: Ticket<Popular>.default, selectedSeats: [])\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Yes I know, bear with me, we will fix those errors shortly .\"), mdx(\"h2\", null, \"Ticket\"), mdx(\"p\", null, \"In the Models folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Ticket.swift\"), \" with the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct Ticket<T: Movie>: Identifiable {\\n    var id: UUID\\n    var movie: T\\n    var date: TicketDate\\n    var hour: String\\n    \\n    static var `default`: Ticket<Popular>{\\n        .init(id: UUID(), movie: Popular.default, date: TicketDate.default, hour: \\\"\\\")\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"Ticket View\"), mdx(\"p\", null, \"In the Views folder, add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TicketView.swift\"), \" file containing the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct TicketView<T: Movie>: View {\\n\\n    var ticket: Ticket<T>\\n    var seat = Seat.default\\n   \\n    var body: some View {\\n      VStack(spacing: 0) {\\n        TopTicketView(ticket: ticket, seat: seat)\\n                      .background(Color.background)\\n                      .clipShape(TicketShape())\\n                  .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)\\n\\n                  DashedSeperator()\\n                      .stroke(Color.gray, style:  StrokeStyle(lineWidth: 1,dash: [4,8], dashPhase: 4))\\n                      .frame(height: 0.4)\\n                      .padding(.horizontal)\\n                  \\n                  BottomTicketView()\\n                      .background(Color(\\\"barcodeBG\\\"))\\n                   .clipShape(TicketShape().rotation(Angle(degrees: 180)))\\n                  .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)\\n\\n      }.padding()\\n    }\\n}\\n\\nstruct TicketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TicketView<Popular>(ticket: Ticket<Popular>.default)\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"You will get even more errors with this, so let\\u2019s create the missing files.  In your views folder, create the following files : \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TopTicketView.swift\"), \" , \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TicketDetailView.swift\\xA0\"), \"  and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"BottomTicketView.swift\"), \", then in the Shapes folder create these \\u2014\", \">\", \" \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DashedSeperator.swift\"), \"  and  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TicketShape.swift\")), mdx(\"p\", null, \"Next, I will paste the code, and specify which file you will put it in:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"TopTicketView.swift\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct TopTicketView<T: Movie>: View {\\n    \\n    var ticket: Ticket<T>\\n    var seat = Seat.default\\n    \\n    var body: some View {\\n        VStack{\\n            VStack(alignment: .leading) {\\n                Text(ticket.movie.studio ?? \\\"Unknown\\\")\\n                    .font(.system(size: 20, weight: .bold))\\n                    .foregroundColor(Color.gray)\\n                Text(ticket.movie.title)\\n                    .font(.system(size: 30, weight: .black))\\n            }.frame(minWidth: 0.0, maxWidth:.infinity, alignment: .leading)\\n                .padding(.top, 30)\\n                .padding(.horizontal)\\n        \\n            Image(uiImage: UIImage(named: \\\"\\\\(ticket.movie.image)_land.jpg\\\")!)\\n                .resizable().frame(minWidth: 0.0, maxWidth: .infinity)\\n                .scaledToFit()\\n                \\n            HStack{\\n                DetailsView(detail1: \\\"SCREEN\\\", detail2: \\\"18\\\", detail3: \\\"PRICE\\\", detail4: \\\"$5.68\\\")\\n                DetailsView(detail1: \\\"ROW\\\", detail2: \\\"\\\\(seat.row)\\\", detail3: \\\"DATE\\\", detail4: \\\"\\\\(ticket.date.day)/\\\\(ticket.date.month)/\\\\(ticket.date.year)\\\")\\n                DetailsView(detail1: \\\"SEAT\\\", detail2: \\\"\\\\(seat.number)\\\", detail3: \\\"TIME\\\", detail4: ticket.hour)\\n            }.padding(.vertical)\\n        }\\n    }\\n}\\n\\nstruct TopTicketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TopTicketView(ticket: Ticket<Popular>.default)\\n    }\\n}\\n\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\xA0TicketDetailView.swift\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct TicketDetailView: View {\\n     var detail1  = \\\"SEAT\\\"\\n      var detail2 = \\\"34\\\"\\n      var detail3 = \\\"TIME\\\"\\n      var detail4 = \\\"18:15\\\"\\n      \\n      var body: some View {\\n          VStack(spacing: 10){\\n              VStack {\\n                  Text(detail1)\\n                      .font(.system(size: 15, weight: .bold))\\n                      .foregroundColor(Color.gray)\\n                  Text(detail2)\\n                  .font(.system(size: 30, weight: .black))\\n\\n              }\\n                  \\n              VStack {\\n                  Text(detail3)\\n                      .font(.system(size: 15, weight: .bold))\\n                      .foregroundColor(Color.gray)\\n                  Text(detail4).font(.system(size: 15, weight: .bold))\\n              }\\n        }.frame(minWidth: 0.0, maxWidth: .infinity)\\n      }\\n}\\n\\nstruct TicketDetailView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TicketDetailView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\xA0BottomTicketView.swift\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct BottomTicketView: View {\\n    var body: some View {\\n         Image(\\\"Barcode\\\")\\n                       .resizable()\\n                       .scaledToFit()\\n                       .padding(30)\\n                       .frame(minWidth: 0.0, maxWidth: .infinity)\\n            .foregroundColor(Color.gray)\\n    }\\n}\\n\\nstruct BottomTicketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BottomTicketView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"DashedSeperator.swift\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct DashedSeperator: Shape {\\n    func path(in rect: CGRect) -> Path {\\n        var path = Path()\\n        path.move(to: CGPoint(x: rect.origin.x, y: rect.origin.y))\\n        path.addLine(to: CGPoint(x: rect.size.width, y: rect.origin.y ))\\n        path.closeSubpath()\\n        return path\\n    }\\n}\\n\\n\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"TicketShape.swift\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct TicketShape: Shape {\\n    func path(in rect: CGRect) -> Path {\\n        let arcRadius: CGFloat = 20\\n        let smallArcRadius:CGFloat = 10\\n        \\n        var path = Path()\\n        path.move(to: CGPoint(x: rect.origin.x, y: rect.origin.y + arcRadius))\\n        path.addArc(center: CGPoint.zero, radius: arcRadius, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 0) , clockwise: true)\\n        \\n        path.addArc(center: CGPoint(x: rect.midX, y: rect.origin.y) , radius: arcRadius, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 0) , clockwise: true)\\n        \\n        path.addLine(to:  CGPoint(x: rect.size.width - arcRadius, y: rect.origin.y))\\n        path.addArc(center: CGPoint(x: rect.size.width , y: rect.origin.y), radius: arcRadius, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 90) , clockwise: true)\\n        \\n        path.addLine(to:  CGPoint(x: rect.size.width, y: rect.size.height - smallArcRadius))\\n        path.addArc(center: CGPoint(x: rect.size.width , y: rect.size.height), radius: smallArcRadius, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 180) , clockwise: true)\\n            \\n        path.addLine(to:  CGPoint(x: rect.origin.x + smallArcRadius, y: rect.size.height))\\n        path.addArc(center: CGPoint(x: rect.origin.x , y: rect.size.height), radius: smallArcRadius, startAngle: Angle(degrees: 360), endAngle: Angle(degrees: 270) , clockwise: true)\\n             \\n        path.closeSubpath()\\n        return path\\n    }\\n}\\n\\nstruct TicketShape_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            TicketShape()\\n        }\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Again check out \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"#\"\n  }), \"this article\"), \" to learn how I built the Ticket View and the scrolling effect.  You should be able to run the app, and everything should work as intended.  \"), mdx(\"h2\", null, \"Payment View\"), mdx(\"p\", null, \"When the user presses the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"buy button\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BasketView\"), \", we will show the Payment screen.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Screens\"), \" folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PaymentView.swift\"), \". At the top of the struct, add the following properties:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  private let paymentMethods = PaymentMethod.allCases\\n    @State private var selectedMethod  = PaymentMethod.MasterCard\\n    @State private var cardholderName  = \\\"\\\"\\n    @State private var cardNumber  = \\\"\\\"\\n    @State private var expireDate  = \\\"\\\"\\n    @State private var cvv  = \\\"\\\"\\n    @State private var formYOffset: CGFloat = 0\\n    @State private var isSendingPending = false\\n    @State private var paymentSent = false\\n    \\n\")), mdx(\"p\", null, \"You don\\u2019t have  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PaymentMethod\"), \" enum yet,  so add a swift file inside the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Models\"), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PaymentMethod.swift\"), \", and put the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nenum PaymentMethod: String, CaseIterable{\\n    case MasterCard,Visa, Paypal\\n}\\n\")), mdx(\"p\", null, \"Go back in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PaymentView.swift\"), \" file, and add the following method below body:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createPaymentMethodSection() -> some View{\\n        return Section(header: Text(\\\"Select payment method\\\").font(.system(size: 20, weight: Font.Weight.semibold)) ) {\\n            Picker(\\\"\\\", selection: self.$selectedMethod) {\\n                ForEach(paymentMethods, id: \\\\.self){ method in\\n                    Text(method.rawValue).tag(method)\\n                }\\n            }.pickerStyle(SegmentedPickerStyle())\\n                .padding()\\n        }\\n    }\\n\")), mdx(\"p\", null, \"The above code creates a section containing a picker that users will use to select which type of payment the want to utilise. Now, let\\u2019s add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Form\"), \" that will contain the above section. In body, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  fileprivate func createNavigationContent() -> some View{\\n        return NavigationView{\\n            Form{\\n                createPaymentMethodSection()\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"Then replace everything in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"createNavigationContent()\\n\")), mdx(\"p\", null, \"Here is a preview:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1579637680/movie%20store/clmelnqq5vavppwbbfpa.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Then below the method you\\u2019ve just created, add this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createDetailsSection() -> some View{\\n            let isPaypal = selectedMethod == PaymentMethod.Paypal\\n            \\n            return Section(header: Text(\\\"Credit card details\\\").font(.system(size: 20, weight: Font.Weight.semibold)) , footer: LCButton(text: \\\"Pay\\\", action: {\\n            }) ) {\\n                LCTextfield(value: self.$cardholderName, placeholder: isPaypal ? \\\"Username\\\" : \\\"Cardholder Name\\\", leadingIcon: Image(systemName: \\\"person\\\"))\\n                HStack {\\n                    LCTextfield(value: self.$cardNumber, placeholder:  isPaypal ? \\\"Password\\\" : \\\"Card Number\\\", leadingIcon: Image(systemName: isPaypal ? \\\"lock\\\" : \\\"number\\\"), isSecure: isPaypal)\\n                    Image(self.selectedMethod.rawValue)\\n                        .resizable()\\n                        .aspectRatio(contentMode: .fit)\\n                        .frame(height: 40)\\n                    \\n                }\\n                \\n                if !isPaypal{\\n                    LCTextfield(value: self.$expireDate, placeholder: \\\"Expire Date\\\", leadingIcon: Image(systemName: \\\"calendar\\\"))\\n                    LCTextfield(value: self.$cvv, placeholder: \\\"CVV\\\", leadingIcon: Image(systemName:  \\\"lock\\\"))\\n                }\\n            }\\n        }\\u2028\\n\")), mdx(\"p\", null, \"This code just introduced a bunch of errors, so let\\u2019s fix them by creating a new swift ui view named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LCTextfield.swift\"), \" containing the following:\"), mdx(\"h2\", null, \"Custom textfield\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct LCTextfield: View {\\n    \\n    @Binding var value: String\\n    var placeholder = \\\"Placeholder\\\"\\n    var leadingIcon = Image(systemName: \\\"person.crop.circle\\\")\\n    var isSecure = false\\n    var onEditingChanged: ((Bool)->()) = {_ in }\\n    \\n    var body: some View {\\n        HStack {\\n            leadingIcon.imageScale(.large)\\n                .padding(.vertical)\\n                        .foregroundColor(Color.gray)\\n            if isSecure{\\n                SecureField(placeholder, text: self.$value, onCommit: {\\n                    self.onEditingChanged(false)\\n                }).padding(.vertical)\\n            } else {\\n                TextField(placeholder, text: self.$value, onEditingChanged: { flag in\\n                    self.onEditingChanged(flag)\\n                }).padding(.vertical)\\n            }\\n        }\\n    }\\n}\\n\\nstruct LCTextfield_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LCTextfield(value: .constant(\\\"\\\"))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"We\\u2019ve just made a reusable textfield that we reused in our form. Just (COMMAND + B) to build the project if the error does not go away automatically.\"), mdx(\"p\", null, \"Then call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createDetailsSection\"), \" method below the payment method in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createNavigationContent\\xA0\"), \". Its content should now look like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"NavigationView{\\n            Form{\\n                createPaymentMethodSection()\\n                createDetailsSection()\\n            }\\n        }\\n\")), mdx(\"p\", null, \" And here is what the preview renders now:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1579639000/movie%20store/mabc0vl9vykwuy3yzea6.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now add the following modifier directly to the form:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".navigationBarTitle(\\\"Payment\\\", displayMode: self.formYOffset == 0 ? .large : .inline)\\n .offset(y: self.formYOffset)\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"\\u201CformYOffset\\u201D\"), \" will change when the keyboard is shown, and will set to zero when the keyboard is hidden.  Add the following code  after the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".offset\"), \" modifier:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  .blur(radius: self.isSendingPending ? 7 : 0).overlay(\\n                               VStack{\\n                                   if self.isSendingPending {\\n                                       createPopupContent()\\n                                   } else {\\n                                       EmptyView()\\n                                   }\\n                               }.frame(maxWidth: .infinity, maxHeight: .infinity)\\n                                   .background( self.isSendingPending ? Color.background.opacity(0.3) : .clear).onAppear(perform: {\\n                                       print(\\\"Runs\\\")\\n                                   })\\n                       )\\n\")), mdx(\"p\", null, \"The above code creates a popup, like we did earlier. We will need to add the function that creates the content, so add the following function below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     fileprivate func createPopupContent() -> some View {\\n        return VStack {\\n            if paymentSent{\\n                self.okView.transition(.hearbeat)\\n                Text( \\\"Success!\\\").foregroundColor(.gray).padding()\\n            } else {\\n                self.loadingView.frame(width: 50, height: 50).transition(.scale)\\n                Text( \\\"Validating...\\\").foregroundColor(.gray).padding()\\n            }\\n        }.frame(width: UIScreen.main.bounds.width * 0.4, height: UIScreen.main.bounds.width * 0.4)\\n            .padding()\\n            .background(Color.background.opacity(0.7))\\n            .cornerRadius(20)\\n            .shadow(color: Color.textColor.opacity(0.3), radius: 20, x: 0, y: 10)\\n            .transition(.move(edge: .bottom))\\n    }\\n    \\n\")), mdx(\"p\", null, \"This code does the following:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Stacks  the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"okView\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text()\"), \" vertically. We will create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"okView\"), \" shortly\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Sets the frame of the pop up card to be 40% the width and height of the entire screen\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Applies the same transition we applied earlier.\")), mdx(\"p\", null, \"Now, put these 2 properties above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  private let okView = OkView(width: 30, lineWidth: 7)\\n  private let loadingView = LoadingView(lineWidth: 7)\\n\")), mdx(\"p\", null, \"To fix those errors, you will need to add a couple of Views in you Views folder. Create 2 swift files named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"OkView.swift\\xA0\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LoadingView.swift\"), \". \"), mdx(\"h2\", null, \"Ok View\"), mdx(\"p\", null, \"Add the following code in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"OkView.swift\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct OkView: View {\\n    var width: CGFloat = 30\\n    var lineWidth: CGFloat = 7\\n    var body: some View {\\n          OkShape()\\n            .stroke(style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round,lineJoin: .round))\\n              .fill(LinearGradient(gradient: Gradient(colors: [Color.accent, .darkPurple]) , startPoint: .leading, endPoint: .trailing))\\n            .frame(width: self.width, height: self.width * 2).rotationEffect(Angle(degrees: 45) )\\n          \\n    }\\n}\\n\\nstruct OkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        OkView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"okShape\"), \" is custom shape that we will create later.  We use that to create the view that we used on the pop-up:\"), mdx(\"h2\", null, \"Ok Shape\"), mdx(\"p\", null, \"Create another swift file named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"OkShape.swift\"), \" in the Shapes folder containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct OkShape: Shape {\\n         \\n    func path(in rect: CGRect) -> Path {\\n        \\n        return Path{ path in\\n            path.move(to: CGPoint(x: rect.origin.x, y: rect.size.height))\\n            path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height))\\n            path.addLine(to: CGPoint(x: rect.size.width, y: rect.origin.y))\\n        }\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code draws an okShape. Just take a minute to read and understand the code. It\\u2019s pretty simple.\"), mdx(\"p\", null, \"That\\u2019s  it for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"OkView\"), \".Let\\u2019s implement the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LoadingView.swift\"), \".\"), mdx(\"h2\", null, \"Loading View\"), mdx(\"p\", null, \" Create a file named  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LoadingView.swift\"), \"  , and  add the following code inside it: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct LoadingView: View {\\n    \\n    var lineWidth: CGFloat = 3\\n    \\n    @State private var flag = false\\n    \\n    var body: some View {\\n        \\n        return VStack {\\n            Circle()\\n                .trim(from: 0, to: 1)\\n                .stroke(style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round, dash: [10,10], dashPhase: flag ? 0 : 40))\\n                .fill(LinearGradient(gradient: Gradient(colors: [Color.accent, .darkPurple]) , startPoint: .leading, endPoint: .trailing))\\n                .animation(Animation.linear(duration: 0.5).repeatForever(autoreverses: false))\\n                .onAppear {\\n                    self.flag = true\\n                    \\n            }.onDisappear {\\n                self.flag = false\\n            }\\n        }\\n    }\\n    \\n    func stop() {\\n        self.flag = false\\n    }\\n}\\n\\nstruct LoadingView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LoadingView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The only thing to note here is how we animate dashes. We do so by setting  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dashPhase\"), \" to 40 and animating it indefinitely. Once the view appears, we will initiate the animation by setting the flag to true in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \" closure.\"), mdx(\"p\", null, \"We will need to create that custom transition to make all of the errors go away.  Let\\u2019s create a new file in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AnyTransitionExt.swift\"), \", and inside it, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nextension AnyTransition {\\n    static var hearbeat: AnyTransition {\\n        return AnyTransition.scale(scale: 1.7).combined(with: .scale(scale: 1))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"As you can see we combine 2 scale transition to create an heartbeat effect. Now you can build the project, and hopefully everything should be okay.\"), mdx(\"p\", null, \"The next and last step is to manage the keyboard properly, but first  go back to the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"BasketView.swift\"), \", and add the following modifier directly to the buy button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".sheet(isPresented: self.$showPaymentScreen, content: {\\n                    PaymentView()\\n                })\\n\")), mdx(\"p\", null, \"To test, run the app, and navigate to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PaymentView\"), \".\"), mdx(\"h2\", null, \"Simulate Payment\"), mdx(\"p\", null, \"Add the following method below  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  fileprivate func simulatePayment() {\\n        \\n        if !cardNumber.isEmpty\\n        && !expireDate.isEmpty\\n        && !cardholderName.isEmpty\\n        && !cvv.isEmpty{\\n            self.isSendingPending = true\\n            \\n            Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in\\n                withAnimation {\\n                    self.paymentSent = true\\n                }\\n                Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in\\n                    withAnimation {\\n                        self.isSendingPending = false\\n                    }\\n                }\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"The above code just checks if inputs are valid. We don\\u2019t do advanced validations, and only check whether the textfields are empty.  \"), mdx(\"p\", null, \"Now call that method in the Pay button action:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"LCButton(text: \\\"Pay\\\", action: {\\n        // Call this in here!\\n        self.simulatePayment() \\n}\\n\")), mdx(\"h2\", null, \"Keyboard management in swift UI\"), mdx(\"p\", null, \"There are lots of tricks that one might try to manage the keyboard in swift ui. One might use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onEditingChanged\"), \" callback to know when the keyboard shows and move the content to accommodate it. This callback will be called when the user begins editing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"text\"), \" and after the user finishes editing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"text\"), \", passing a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Bool\"), \" indicating whether \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"self\"), \"  (the textfield) is currently being edited or not. The problem with this trick is that the\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SecureField\"), \" does not have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onEditingChanged\"), \" callback.\"), mdx(\"p\", null, \"Another way to handle the keyboard is to use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NotificationCenter\"), \" events. We will combine publishers with NotificationCenter to send events when the keyboard shows and hides. There is view in swift ui that was made just to handle published event, and it\\u2019s called \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SubscriptionView\"), \". \"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NotificationCenterExt.swift\"), \", and put the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport UIKit\\n\\nextension NotificationCenter {\\n    private static let keyboardWillShow = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillShowNotification)\\n    private static let keyboardWillHide = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillHideNotification)\\n    \\n    static var keyboardEvent = NotificationCenter.keyboardWillShow.merge(with: NotificationCenter.keyboardWillHide.map { Notification(name: $0.name, object: $0.object, userInfo: nil) })\\n        .map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero)}\\n}\\n\\n\")), mdx(\"p\", null, \"In the above code, we create 2 publishers, then merge them together to create the keyboardEvent. We publish the keyboard\\u2019s frame in the event .\"), mdx(\"p\", null, \"Now to subscribe to those events, we will need to use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SubscriptionView\"), \". Here is its construction:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"init(content: Content, publisher: PublisherType, action: @escaping (PublisherType.Output) -> Void)\\n\")), mdx(\"p\", null, \"The view takes the content to be rendered, a publisher to subscribe to and an action that will handle the event.\"), mdx(\"p\", null, \"So replace the content in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  SubscriptionView(content:  createNavigationContent(), publisher: NotificationCenter.keyboardEvent ) { rect in\\n            withAnimation {\\n                self.formYOffset = rect.height > 0 ? -230 : 0\\n            }\\n      }\\n\")), mdx(\"p\", null, \"In the above code, we receive the keyboard\\u2019s frame from the publisher, and animate the form by setting the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"formYOffset\"), \" state.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"This is the end of this series. Stay tuned for many more. People have asked me how one can use real movie data fetched from a remote api. I might add a bonus tutorial explaining how to do just that. I will replace the sample data with a real one. Happy coding!!! \"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#intro","title":"Intro"},{"url":"#basic-validation","title":"Basic Validation"},{"url":"#popup-view","title":"Popup View"},{"url":"#basket-view","title":"Basket View"},{"url":"#ticket","title":"Ticket"},{"url":"#ticket-view","title":"Ticket View"},{"url":"#payment-view","title":"Payment View"},{"url":"#custom-textfield","title":"Custom textfield"},{"url":"#ok-view","title":"Ok View"},{"url":"#ok-shape","title":"Ok Shape"},{"url":"#loading-view","title":"Loading View"},{"url":"#simulate-payment","title":"Simulate Payment"},{"url":"#keyboard-management-in-swift-ui","title":"Keyboard management in swift UI"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T17:31:26.545Z","isNewLayout":null,"previews":null,"slug":"swift-ui-movie-booking-app-part-1","title":"UIViewRepresentable & UICollectionViewCompositionalLayout","updatedDate":"2019-12-30T00:00+01:00","downloads":[{"url":"#","type":"zip","id":"70c6d1a0-f24d-5980-aa8a-9720e51a2ed7","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, \"This is part 1 of the movie booking app series. In this part, we will create the home screen which will display a collectionView of movies. The source code of this series will not be public on GitHub. Click the floating button located in the bottom-left corner to download the source code. The folder contains 2 projects, a starter project which contains all of the images and data we will use to display movies and a finished one for this first part. You will need the starter project to follow along.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 11 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Catalina or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 13 or greater\")), mdx(\"h2\", null, \"Get started\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Open Xcode\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Create a new Xcode project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Select single view app and click next\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Name your app and make sure the user interface is Swift UI\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Last, click Finish.\")), mdx(\"h2\", null, \"Models\"), mdx(\"p\", null, \"In the Xcode root folder, create a Models folder and inside it add \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieBundle.swift\"), \"  file containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct MovieBundle: Codable, Hashable {\\n    let trending: [Trending]\\n    let popular: [Popular]\\n    let actors: [Actor]\\n    let upcoming: [Upcoming]\\n}\\n\\nprotocol Movie: Codable, Hashable {\\n    var id: Int { get }\\n    var title: String { get }\\n    var releaseDate: String { get }\\n    var description: String { get }\\n    var image: String { get }\\n    var rating: Double { get }\\n    var genres: [String] { get }\\n    var runtime: String { get }\\n    var studio: String? { get }\\n}\\n\\n\\n// MARK: - Trending\\nstruct Trending: Movie {\\n    let id: Int\\n    let title, releaseDate, description, image: String\\n    let rating: Double\\n    let genres: [String]\\n    let runtime: String\\n    var studio: String? = \\\"\\\"\\n    \\n    static var `default`: Trending{\\n        .init(id: 0, title: \\\"\\\", releaseDate: \\\"\\\", description: \\\"\\\", image: \\\"\\\", rating: 0, genres: [], runtime: \\\"\\\", studio: \\\"\\\")\\n    }\\n}\\n\\n\\n// MARK: - Actor\\nstruct Actor: Codable, Hashable {\\n    let id: Int\\n    let name, bio, image: String\\n    \\n    static var `default`: Actor{\\n        .init(id: 0, name: \\\"\\\", bio: \\\"\\\", image: \\\"\\\")\\n    }\\n\\n}\\n\\n// MARK: - Popular\\nstruct Popular: Movie {\\n    let id: Int\\n    let title, releaseDate, description, image: String\\n    let rating: Double\\n    let genres: [String]\\n    let runtime: String\\n    var studio: String? = \\\"\\\"\\n    \\n    static var `default`: Popular{\\n        .init(id: 0, title: \\\"\\\", releaseDate: \\\"\\\", description: \\\"\\\", image: \\\"\\\", rating: 0, genres: [], runtime: \\\"\\\", studio: \\\"\\\")\\n    }\\n\\n}\\n\\n// MARK: - Upcoming\\nstruct Upcoming: Movie {\\n    let id: Int\\n    let title, releaseDate, description, image: String\\n    let rating: Double\\n    let genres: [String]\\n    let runtime: String\\n    var studio: String? = \\\"\\\"\\n    \\n    static var `default`: Upcoming{\\n        .init(id: 0, title: \\\"\\\", releaseDate: \\\"\\\", description: \\\"\\\", image: \\\"\\\", rating: 0, genres: [], runtime: \\\"\\\", studio: \\\"\\\")\\n    }\\n}\\n\\n\\n\")), mdx(\"p\", null, \"Couple of things to note here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The MovieBundle struct is the one that we will use to decode the data.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Movie\"), \" protocol that all our movies must conform to, you will see why in the second part of the series.\\nThat\\u2019s our models. Let\\u2019s create the ViewModel then.\")), mdx(\"h2\", null, \"ViewModel\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nenum HomeSection: String, CaseIterable {\\n    case Trending\\n    case Popular\\n    case Upcoming\\n    case Actors\\n}\\n\\n\\nclass MovieViewModel: ObservableObject {\\n    \\n    @Published var allItems: [HomeSection:[Codable]] = [:]\\n    \\n    init() {\\n        getAll()\\n    }\\n    \\n    private func getAll(){\\n        if let path = Bundle.main.path(forResource: \\\"data\\\", ofType: \\\"json\\\") {\\n            \\n            do {\\n                let url = URL(fileURLWithPath: path)\\n                let data = try Data(contentsOf: url, options: .mappedIfSafe)\\n                let decoder = JSONDecoder()\\n                let result = try decoder.decode(MovieBundle.self, from: data)\\n                allItems = [HomeSection.Trending: result.trending,\\n                            HomeSection.Popular: result.popular,\\n                            HomeSection.Upcoming: result.upcoming,\\n                            HomeSection.Actors: result.actors]\\n                \\n            } catch let e{\\n                print(e)\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is a brief rundown of the above code:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HomeSection\"), \" enum which will be used as keys to retrieve specific section from the model.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then in the ViewModel, we read the file containing the data, decode it and set the published property to notify interested views so that they can reload and reflect the current state of  the model.\")), mdx(\"h2\", null, \"UIViewRepresentable\"), mdx(\"p\", null, \"If you take a look a the video or just open the App Store\\u2019s game tab and try scrolling, you will notice that no matter how hard you scroll, you will only scroll one item at a time creating a peekaboo effect. To achieve that effect, we must use UIKit\\u2019s UICollectionView combined with the newly introduced UICollectionViewCompositionalLayout. \"), mdx(\"p\", null, \"In you Xcode\\u2019s root folder, create a folder named UIKit. This is where we are going to keep all UIKit related files. In that folder, create a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieCollectionView.swift\"), \" \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\n\\nstruct MovieCollectionView: UIViewRepresentable {}\\n\")), mdx(\"p\", null, \"You will get a compiler error, but it\\u2019s okay we will fix that shortly. Notice how we conform to the UIViewRepresentable protocol, this might be familiar if you\\u2019ve read the article where I showed how to create an onboarding screen in swift ui. If you haven\\u2019t read it, \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/swiftui-onboarding-screen/\",\n    \"title\": \"Onboarding screen in swift ui\"\n  }), \"here it is\"), \". It sort of does the same thing except that we want to use a UIView rather than a UIViewController in swift UI.\"), mdx(\"p\", null, \"Add this code inside that struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func makeUIView(context: Context) -> UICollectionView {\\n    \\n        return UICollectionView()\\n    }\\n\\n    func updateUIView(_ collectionView: UICollectionView, context: Context) {\\n    \\n    }\\n\")), mdx(\"p\", null, \"In the first method is where we are going to initialise whatever view we want to use in swiftUI, in this case, it\\u2019s a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UICollectionView\"), \". Treat the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeUIView\"), \" method as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init(frame: CGRect)\"), \" in a normal UIKit UIView. The second view is where you would normally update the view that you want to use in swiftUI. There\\u2019s 2 other methods, the first one being \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeCoordinator()\"), \" which we will learn more of shortly, and the last one is called \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dismantleUIView(uiView: Self.UIViewType, coordinator: Self.Coordinator)\"), \" which you would normally use when this view is about  to be killed, and want to clean up resources to avoid memory leaks and so on\\u2026\"), mdx(\"h2\", null, \"Coordinator\"), mdx(\"p\", null, \"The coordinator will be the class that will coordinate, as its name implies, the interaction between swift UI with whatever UIView we are using. This is the class that will handle all the datasource and delegate conformance.\"), mdx(\"p\", null, \"Add the following class inside the same struct, below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updateUIView\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"class Coordinator: NSObject,UICollectionViewDataSource, UICollectionViewDelegate{\\n\\n    var parent: MovieCollectionView\\n        \\n    init(_ parent: MovieCollectionView) {\\n            self.parent = parent\\n        }\\n        \\n}\\n\")), mdx(\"p\", null, \"As I mentioned earlier, we conform the coordinator to  the UICollectionView datasource and delegate. We also keep a reference to the parent struct which  is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MovieCollectionView\"), \"\\u2026  You will see why in a moment, but first, override the following methods to silence the compiler error you got.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"   func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {\\nreturn 0\\n        }\\n        \\n        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {\\nreturn UICollectionViewCell()\\n        }\\n        \\n\")), mdx(\"p\", null, \"In the same class, add the following method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" func createCompositionalLayout() -> UICollectionViewLayout {\\n      return UICollectionViewFlowLayout()\\n\\n}\\n\")), mdx(\"p\", null, \"We return the standard FlowLayout for now, but we will change with the UICollectionViewCompositionalLayout later on.\\nNow add the following method in \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieCollectionView.swift\"), \" above the coordinator class: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  func makeCoordinator() -> MovieCollectionView.Coordinator {\\n        Coordinator(self)\\n    }\\n\")), mdx(\"p\", null, \"This is the fourth method from the UIViewRepresentable protocol. Its only job is to initialise a new Coordinator instance that we will use shortly.\\nAfter you\\u2019ve done that, add the following in the\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeUIView\"), \" method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"let collectionView = UICollectionView(frame: .zero, collectionViewLayout: context.coordinator.createCompositionalLayout())\\n        collectionView.backgroundColor = .clear\\n   \\n        collectionView.dataSource = context.coordinator\\n        collectionView.delegate = context.coordinator\\n        collectionView.alwaysBounceVertical = true\\n        collectionView.showsVerticalScrollIndicator = false\\n\\nreturn collectionView\\n\")), mdx(\"p\", null, \"A few things to note in the above code: \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We retrieve the coordinator from the context which in turn gives us access to everything in the coordinator class.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We set the datasource and delegate to the coordinator because the coordinator conforms to those 2 protocols.\\nBefore we continue, we need to create UICollectionView cells for each section.\")), mdx(\"h2\", null, \"Trending Cell\"), mdx(\"p\", null, \"In the UIKit folder, create a TrendingCell.swift file, and put the following inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport UIKit\\n\\nclass TrendingCell: UICollectionViewCell {\\n    static let reuseId: String = \\\"TrendingCell\\\"\\n    var trending: Trending?{\\n        didSet{\\n            if let trending = self.trending {\\n                imageView.image = UIImage(named: \\\"\\\\(trending.image)_land.jpg\\\")\\n                titleLabel.text = trending.title\\n            }\\n        }\\n    }\\n    \\n    lazy var imageView: UIImageView = {\\n        let imageView = UIImageView()\\n        imageView.contentMode = .scaleAspectFill\\n        imageView.image = UIImage(named: \\\"adastra_land.jpg\\\")\\n        imageView.translatesAutoresizingMaskIntoConstraints = false\\n        imageView.backgroundColor = .clear\\n        imageView.clipsToBounds = true\\n        imageView.layer.cornerRadius = 10\\n        return imageView\\n    }()\\n    \\n    lazy var titleLabel: UILabel = {\\n        let label = UILabel()\\n        label.text = \\\"Ad Astra\\\"\\n        label.textColor = UIColor(named: \\\"textColor\\\")\\n        label.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .bold))\\n        label.translatesAutoresizingMaskIntoConstraints = false\\n        label.numberOfLines = 2\\n        label.textColor = .white\\n        return label\\n    }()\\n\\n    override init(frame: CGRect) {\\n        super.init(frame: frame)\\n        setupCell()\\n    }\\n    \\n    required init?(coder: NSCoder) {\\n        super.init(coder: coder)\\n    }\\n    \\n    \\n    \\n      private func setupCell(){\\n          let gradientView = UIView(frame: CGRect(x: 0, y:  self.frame.height / 4 , width: self.frame.width, height: self.frame.height / 2) )\\n          gradientView.layer.cornerRadius = 20\\n          let gradient = CAGradientLayer()\\n          gradient.frame = gradientView.frame\\n          gradient.colors = [UIColor.clear, UIColor.black.cgColor]\\n          \\n          gradientView.layer.insertSublayer(gradient, at: 0)\\n\\n          contentView.addSubview(self.imageView)\\n          self.imageView.addSubview(gradientView)\\n          contentView.addSubview(titleLabel)\\n              \\n          \\n          NSLayoutConstraint.activate([\\n              imageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),\\n              imageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),\\n              imageView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9),\\n              imageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1),\\n              \\n              titleLabel.leadingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: 10),\\n              titleLabel.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),\\n              titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),\\n              titleLabel.heightAnchor.constraint(equalToConstant: 50)\\n          ])\\n\\n      }\\n    \\n}\\n\\n\\n\")), mdx(\"p\", null, \"If you have worked with UIKit before this code will be simple, but if you haven\\u2019t, I  will briefly run through what I have done. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"I initialise and configure views lazily using closure to make the code a bit cleaner, but also for performance purposes. Read more about  \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://www.hackingwithswift.com/example-code/language/what-are-lazy-variables%5C\",\n    \"title\": \" lazy variables on Paul Hudson’s Hacking with swift website\"\n  }), \"lazy variables on Paul Hudson\\u2019s Hacking with swift website\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the setup cell method, I constrain those 2 views to the contentView after adding them to it. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"I also created a gradient view at the bottom of the cell which will lay behind the title.\\nHere is what we\\u2019ve just created:\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577522653/movie%20store/gjbb6fzpy6lz2prrqhzz.png\",\n    \"alt\": \"UICollection compositional layout\"\n  }))), mdx(\"h2\", null, \"Popular Cell\"), mdx(\"p\", null, \"In the same folder, create a PopularCell.swift file and put the following inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nclass PopularCell: UICollectionViewCell {\\n    static let reuseId: String = \\\"PopularCell\\\"\\n    var popular: Popular?{\\n        didSet{\\n            if let movie = self.popular {\\n                imageView.image = UIImage(named: \\\"\\\\(movie.image).jpg\\\")\\n                titleLabel.text = movie.title\\n            }\\n        }\\n    }\\n    \\n    lazy var imageView: UIImageView = {\\n        let imageView = UIImageView()\\n        imageView.contentMode = .scaleAspectFill\\n        imageView.image = UIImage(named: \\\"adastra.jpg\\\")\\n        imageView.translatesAutoresizingMaskIntoConstraints = false\\n        imageView.backgroundColor = .clear\\n        imageView.clipsToBounds = true\\n        imageView.layer.cornerRadius = 10\\n        return imageView\\n    }()\\n    \\n    lazy var titleLabel: UILabel = {\\n        let label = UILabel()\\n        label.text = \\\"Ad Astra\\\"\\n        label.textColor = UIColor(named: \\\"textColor\\\")\\n        label.translatesAutoresizingMaskIntoConstraints = false\\n        label.numberOfLines = 2\\n        label.textColor = .secondaryLabel\\n        return label\\n    }()\\n\\n    override init(frame: CGRect) {\\n        super.init(frame: frame)\\n        setupCell()\\n    }\\n    \\n    required init?(coder: NSCoder) {\\n        super.init(coder: coder)\\n    }\\n    \\n    \\n    private func setupCell(){\\n        \\n        self.addSubview(self.imageView)\\n        self.addSubview(self.titleLabel)\\n        \\n        NSLayoutConstraint.activate([\\n            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\\n            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\\n            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),\\n            imageView.heightAnchor.constraint(equalTo: self.widthAnchor,multiplier: (3/2)),\\n            \\n            titleLabel.topAnchor.constraint(equalTo: self.imageView.bottomAnchor),\\n            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\\n            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\\n            titleLabel.heightAnchor.constraint(equalToConstant: 50)\\n            \\n        ])\\n    }\\n    \\n}\\n\\n\\n\")), mdx(\"p\", null, \"This code is almost similar to the one above, so refer to it if you have trouble understanding.\"), mdx(\"h2\", null, \"Actor Cell\"), mdx(\"p\", null, \"In the same folder, create a PopularCell.swift file and put the following inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport UIKit\\n\\n\\nclass ActorCell: UICollectionViewCell {\\n    static let reuseId: String = \\\"ActorCell\\\"\\n    var actor: Actor?{\\n        didSet{\\n            if let actor = self.actor {\\n                imageView.image = UIImage(named: \\\"\\\\(actor.image).jpg\\\")\\n                titleLabel.text = actor.name\\n            }\\n        }\\n    }\\n    \\n    lazy var imageView: UIImageView = {\\n        let imageView = UIImageView()\\n        imageView.contentMode = .scaleAspectFill\\n        imageView.image = UIImage(named: \\\"adastra.jpg\\\")\\n        imageView.translatesAutoresizingMaskIntoConstraints = false\\n        imageView.backgroundColor = .clear\\n        imageView.clipsToBounds = true\\n        imageView.layer.cornerRadius = 10\\n\\n        return imageView\\n    }()\\n    \\n    lazy var titleLabel: UILabel = {\\n        let label = UILabel()\\n        label.text = \\\"Ad Astra\\\"\\n        label.textColor = .secondaryLabel\\n        label.translatesAutoresizingMaskIntoConstraints = false\\n        label.numberOfLines = 2\\n\\n\\n        return label\\n    }()\\n\\n    override init(frame: CGRect) {\\n        super.init(frame: frame)\\n        setupCell()\\n    }\\n    \\n    required init?(coder: NSCoder) {\\n        super.init(coder: coder)\\n    }\\n    \\n    \\n    private func setupCell(){\\n        \\n        self.addSubview(self.imageView)\\n        self.addSubview(self.titleLabel)\\n        \\n                \\n        NSLayoutConstraint.activate([\\n            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\\n            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\\n            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),\\n            imageView.heightAnchor.constraint(equalTo: self.widthAnchor,multiplier: (3/2)),\\n            \\n            titleLabel.topAnchor.constraint(equalTo: self.imageView.bottomAnchor),\\n            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\\n            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\\n            titleLabel.heightAnchor.constraint(equalToConstant: 50)\\n            \\n        ])\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code is also similar  to the popular cell with the exception of the actor property at the top.\\nBoth of them create the following layout:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577523522/movie%20store/nxsjfolqdu7pjp8by8yy.png\",\n    \"alt\": \"UICollectionView Compositional layout\"\n  }))), mdx(\"h2\", null, \"Upcoming Cell\"), mdx(\"p\", null, \"In the same folder, create a UpcomingCell.swift file and put this inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport UIKit\\n\\n\\nclass UpcomingCell: UICollectionViewCell {\\n    static let reuseId: String = \\\"UpcomingCell\\\"\\n    var upcoming: Upcoming?{\\n        didSet{\\n            if let upcoming = self.upcoming {\\n                imageView.image = UIImage(named: \\\"\\\\(upcoming.image)_land.jpg\\\")\\n                titleLabel.text = upcoming.title\\n                releaseDateLabel.text = upcoming.releaseDate\\n            }\\n        }\\n    }\\n    \\n    lazy var imageView: UIImageView = {\\n        let imageView = UIImageView()\\n        imageView.contentMode = .scaleAspectFill\\n        imageView.image = UIImage(named: \\\"adastra_land.jpg\\\")\\n        imageView.translatesAutoresizingMaskIntoConstraints = false\\n        imageView.backgroundColor = .clear\\n        imageView.clipsToBounds = true\\n        imageView.layer.cornerRadius = 10\\n        return imageView\\n    }()\\n    \\n    lazy var titleLabel: UILabel = {\\n        let label = UILabel()\\n        label.text = \\\"Ad Astra\\\"\\n        label.textColor = UIColor(named:\\\"textColor\\\")\\n        label.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .bold))\\n        label.numberOfLines = 2\\n        label.translatesAutoresizingMaskIntoConstraints = false\\n        return label\\n    }()\\n    \\n    lazy var releaseDateLabel: UILabel = {\\n        let label = UILabel()\\n        label.text = \\\"December 25, 2019\\\"\\n        label.textColor = UIColor.gray\\n        label.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 15, weight: .regular))\\n        label.numberOfLines = 2\\n        label.translatesAutoresizingMaskIntoConstraints = false\\n        return label\\n    }()\\n\\n\\n    override init(frame: CGRect) {\\n        super.init(frame: frame)\\n        setupCell()\\n    }\\n    \\n    required init?(coder: NSCoder) {\\n        super.init(coder: coder)\\n    }\\n    \\n      private func setupCell(){\\n        contentView.addSubview(self.imageView)\\n        contentView.addSubview(self.titleLabel)\\n        contentView.addSubview(self.releaseDateLabel)\\n                                \\n          NSLayoutConstraint.activate([\\n              imageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),\\n              imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\\n              imageView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.5),\\n              imageView.heightAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.25),\\n              \\n              titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10),\\n              titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),\\n              titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),\\n              titleLabel.heightAnchor.constraint(equalToConstant: 30),\\n\\n              releaseDateLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10),\\n              releaseDateLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),\\n              releaseDateLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),\\n              releaseDateLabel.heightAnchor.constraint(equalToConstant: 15)\\n          ])\\n\\n      }\\n}\\n\")), mdx(\"p\", null, \"The above is works similarly as the above ones, but the layout rendered here will slightly be different. Here it is:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577524068/movie%20store/q0poorjrxfrkilqgdwpz.png\",\n    \"alt\": \"UICollectionView compositional layout\"\n  }))), mdx(\"h2\", null, \"Section Header View\"), mdx(\"p\", null, \"This is the view we will build in UIKit. Add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HeaderView.swift\"), \" , and inside it , add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport UIKit\\n\\nclass HeaderView: UICollectionReusableView {\\n    \\n    static let reuseId = \\\"HeaderView\\\"\\n    var onSeeAllClicked = {}\\n    \\n    lazy var name: UILabel = {\\n          let label = UILabel()\\n          label.text = \\\"Popular\\\"\\n          label.textColor = UIColor(named: \\\"textColor\\\")\\n          label.numberOfLines = 2\\n        label.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .bold))\\n\\n          return label\\n      }()\\n    \\n    lazy var seeAll: UIButton = {\\n        let button  = UIButton(type: .system)\\n        button.setTitle(\\\"See all\\\", for: .normal)\\n        button.setTitleColor(UIColor(named: \\\"darkPurple\\\"), for: .normal)\\n        button.addTarget(self, action: #selector(seeAllMovies), for: .touchUpInside)\\n        button.backgroundColor = .clear\\n        return button\\n    }()\\n\\n\\n    lazy var HStack:UIStackView = {\\n        let stack = UIStackView(arrangedSubviews: [name,seeAll])\\n        stack.translatesAutoresizingMaskIntoConstraints = false\\n        stack.axis = .horizontal\\n        stack.distribution = .equalSpacing\\n        return stack\\n    }()\\n    \\n    \\n    override init(frame: CGRect) {\\n        super.init(frame: frame)\\n        \\n       self.addSubview(HStack)\\n        \\n        NSLayoutConstraint.activate([\\n            HStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),\\n            HStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),\\n            HStack.topAnchor.constraint(equalTo: self.topAnchor),\\n            HStack.bottomAnchor.constraint(equalTo: self.bottomAnchor)\\n        ])\\n    }\\n    \\n    required init?(coder: NSCoder) {\\n         fatalError(\\\"Not happening...\\\")\\n    }\\n    \\n    @objc fileprivate func seeAllMovies(){\\n        self.onSeeAllClicked()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code is similar to what we have done except we have a closure that will run when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" button is clicked. You will see how it will be used in the MovieCollectionView later.  \"), mdx(\"p\", null, \"Here is the header view we\\u2019ve just created:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577526249/movie%20store/hwhuqr4ymvwr7r6t1ecr.png\",\n    \"alt\": \"UIStack View\"\n  }))), mdx(\"p\", null, \"Now that we have the cells out of the way, the start working on the compositional layout.\"), mdx(\"h2\", null, \"UICollectionViewCompositionalLayout\"), mdx(\"p\", null, \"If you\\u2019ve worked with the UICollectionView before, then you are aware of UICollectionViewFlowLayout. With the flow layout, the items in the collection view flow from one row or column (depending on the scrolling direction) to the next, with each row comprising as many cells as will fit.  However, there's a new way of creating complex layouts with ease in UIKit using the UICollectionViewCompositionalLayout.\"), mdx(\"p\", null, \"Before, if one wanted to create a layout like the App Store\\u2019s app or game screen layout, he would combine multiple UICollectionViews scrolled horizontally within a UITableView or a UICollectionView scrolled vertically. However, that\\u2019s not the case anymore with the introduction of the amazing  UICollectionViewCompositionalLayout. \"), mdx(\"p\", null, \"In a nutshell, here is the UICollectionViewCompositionalLayout breakdown:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577630946/movie%20store/vuag09otxr17qfuhzllz.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Let\\u2019s now start creating layouts. We will start the shared layout that will be used by the Popular and Actor section because they have identical  layouts.\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577538850/movie%20store/gj50d4vnufhbfu3xeusm.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"In the coordinator class, below the cellForItemAt method, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       \\n        func createSharedSection() -> NSCollectionLayoutSection {\\n            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))\\n            let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)\\n            layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)\\n            let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension:  .fractionalWidth(0.75))\\n            let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])\\n            let layoutSection = NSCollectionLayoutSection(group: layoutGroup)\\n            layoutSection.orthogonalScrollingBehavior = .groupPagingCentered\\n            layoutSection.boundarySupplementaryItems = [createSectionHeader()]\\n            return layoutSection\\n        }\\n        \\n\")), mdx(\"p\", null, \"Let me explain the above code line by line: \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The itemSize is calculated by taking half of the group\\u2019s width  using  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"\\xA0.fractionalWidth(0.5)\"), \" while the height will be equal to the group\\u2019s full height. Then we create an NSCollectionLayoutItem giving it its size.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The layoutGroupSize is calculated the same way, but uses the section\\u2019s dimensions, and notice how we use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"\\xA0.fractionalWidth(0.75)\"), \" to set the height, this is because we want the height to be \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"3/4\"), \" of the group\\u2019s width.  Then we create the layout group  which will scroll horizontally by by giving it the group size and an array of layout Item.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create an NSCollectionLayoutSection passing in the layout group.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We define the scrolling behaviour by setting the layout section\\u2019s orthogonalScrollingBehavior to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".groupPagingCentered\"), \" which will create the app Store\\u2019s peekaboo effect. Here are all the scrolling behaviour you can try and see how they work:\\nHere is what the layout will look like:  \")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1577538850/movie%20store/gpojqeess3f464rrcrhv.png\",\n    \"alt\": null\n  }))), mdx(\"ol\", {\n    \"start\": 5\n  }, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we create the section header by setting the section\\u2019s boundarySupplementaryItems. You will get a compiler error caused by the missing createSectionHeader method. So add the following code to silence that error:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"            func createSectionHeader() -> NSCollectionLayoutBoundarySupplementaryItem {\\n                   let layoutSectionHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(80))\\n                   let layoutSectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: layoutSectionHeaderSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)\\n                   return layoutSectionHeader\\n            }\\n\")), mdx(\"p\", null, \"You can learn more about compositional layout \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/videos/play/wwdc2019/215/\",\n    \"title\": \"Wwdc 2019 video\"\n  }), \"here\"), \". \"), mdx(\"p\", null, \"To avoid being repetitive, I am not going to add the code for the remaining layout here, but it almost similar to what I\\u2019ve just explained above. Just get the  final project\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"a\", _extends({\n    parentName: \"strong\"\n  }, {\n    \"href\": \"https://mailchi.mp/457f0394a654/liquidcoder\",\n    \"title\": \"Movie booking app - part 1 - Source code \"\n  }), \"HERE\")), \"  if you don\\u2019t have it yet, and  copy the those 2 methods in you code. \"), mdx(\"p\", null, \"So far, we have been preparing the components of our compositional layout, now is the time to create it.  Find the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createCompositionalLayout\"), \" method, and replace the return statement with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        func createCompositionalLayout() -> UICollectionViewLayout {\\n            let layout = UICollectionViewCompositionalLayout{[weak self] index, environment in\\n                switch index{\\n                case 0:\\n                    return self?.createTrendingSection()\\n                case 1:\\n                    return self?.createSharedSection()\\n                case 2:\\n                    return self?.createUpcomingSection()\\n                default:\\n                    return self?.createSharedSection()\\n                }\\n            }\\n            let config = UICollectionViewCompositionalLayoutConfiguration()\\n            config.interSectionSpacing = 20\\n            layout.configuration = config\\n            return layout\\n        }\\n\")), mdx(\"p\", null, \"Voila!! that\\u2019s our compositional layout.  For each section index, we return the appropriate layout, and we set the spacing between section to 20. We are not ready to run the app yet because we still haven\\u2019t implemented the DataSource, so let\\u2019s tackle that now. \"), mdx(\"h2\", null, \"UICollectionViewDataSource\"), mdx(\"p\", null, \" At the top of the viewController struct, add the following properties:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    var allItems: [HomeSection:[Codable]]\\n    var didSelectItem: ((_ indexPath: IndexPath)->()) = {_ in }\\n    var seeAllforSection: ((_ section: HomeSection)->()) = {_ in }\\n\")), mdx(\"p\", null, \"Here is a quick explanation of what they do:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first one is the data that we parsed earlier.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The second one is a closure that will run every time one of the cells is selected.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The third one is obviously also a closure  that will run when the see all button on the  header view is tapped.\\nAnd in \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"makeUIView\"), \",  below the code line that creates the collectionView, add the following code:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        collectionView.register(TrendingCell.self, forCellWithReuseIdentifier: TrendingCell.reuseId)\\n        collectionView.register(PopularCell.self, forCellWithReuseIdentifier: PopularCell.reuseId)\\n        collectionView.register(UpcomingCell.self, forCellWithReuseIdentifier: UpcomingCell.reuseId)\\n        collectionView.register(ActorCell.self, forCellWithReuseIdentifier: ActorCell.reuseId)\\n        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader , withReuseIdentifier: HeaderView.reuseId)\\n\")), mdx(\"p\", null, \"In the above code, we register all of the cells and the headerView. \"), mdx(\"p\", null, \"In the coordinator struct, add the following method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func numberOfSections(in collectionView: UICollectionView) -> Int {\\n            return parent.allItems.count\\n        }\\n\")), mdx(\"p\", null, \"The above code return the number of section, which 4 in our case. Then replace the return statement in numberOfItemsInSection with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"return parent.allItems[HomeSection.allCases[section]]?.count ?? 0\\n\")), mdx(\"p\", null, \"In this method, we return the number of items (Movies or Actors) for each section. And last, before we try a first run, replace everything in cellForItemAt with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {\\n            \\n            switch indexPath.section {\\n            case 0:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TrendingCell.reuseId, for: indexPath) as? TrendingCell{\\n                    cell.trending = parent.allItems[HomeSection.Trending]?[indexPath.item] as? Trending\\n                    return cell\\n                }\\n            case 1:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PopularCell.reuseId, for: indexPath) as? PopularCell{\\n                    cell.popular = parent.allItems[HomeSection.Popular]?[indexPath.item] as? Popular\\n                    return cell\\n                }\\n                \\n            case 2:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UpcomingCell.reuseId, for: indexPath) as? UpcomingCell{\\n                    cell.upcoming = parent.allItems[HomeSection.Upcoming]?[indexPath.item] as? Upcoming\\n                    return cell\\n                }\\n            default:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ActorCell.reuseId, for: indexPath) as? ActorCell{\\n                    cell.actor = parent.allItems[HomeSection.Actors]?[indexPath.item] as? Actor\\n                    return cell\\n                }\\n            }\\n            return UICollectionViewCell()\\n        }\\n\")), mdx(\"p\", null, \"For each section, we dequeue and return the corresponding cells. Now try running the app\\u2026 Only the \\u201CHello world\\u201D shows up, right? Well haven\\u2019t added the MovieCollectionView in swift UI yet. \"), mdx(\"p\", null, \"Open MovieStoreApp, and add this at the top of the file: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@ObservedObject private var model = MovieViewModel()\\n\")), mdx(\"p\", null, \"Then replace the hello world text with this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"let movieCollectionView = createCollectionView().edgesIgnoringSafeArea(.all).navigationBarTitle(\\\"Movies\\\")\\n        \\n        return NavigationView {\\n            movieCollectionView\\n        }\\n\")), mdx(\"p\", null, \"To silence the error you get, put the following below the body block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createCollectionView() -> MovieCollectionView {\\n        return MovieCollectionView(allItems: model.allItems, didSelectItem: { indexPath in }, seeAllforSection: { section in })\\n    }\\n\")), mdx(\"p\", null, \"If you run the app, it looks naked without the section headers. So add the following method anywhere inside the Coordinator class:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n        func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {\\n            switch kind {\\n            case UICollectionView.elementKindSectionHeader:\\n            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderView.reuseId, for: indexPath) as? HeaderView else { return UICollectionReusableView() }\\n                header.name.text =  HomeSection.allCases[indexPath.section].rawValue\\n                header.onSeeAllClicked = { [weak self] in\\n                    self?.parent.seeAllforSection(HomeSection.allCases[indexPath.section])\\n                }\\n                return header\\n            default:\\n                 return UICollectionReusableView()\\n            }\\n        }\\n\")), mdx(\"p\", null, \"The above code creates the header for each section and wires the onSeeAllClicked closure from the HeaderView class to the parent\\u2019s closure. Let\\u2019s also wire the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didSelectItem\"), \".\\nAdd the following below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cellForItemAt\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {\\n            parent.didSelectItem(indexPath)\\n        }\\n\")), mdx(\"p\", null, \"Now, run the app again... everything should look as expected. And dark mode is supported. With this, we say goodbye to UIKit in this series. In the second part, we will work on the detail screens for both movies and actors, so make sure you are subscribed if you haven\\u2019t done so already. Happy Coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#models","title":"Models"},{"url":"#viewmodel","title":"ViewModel"},{"url":"#uiviewrepresentable","title":"UIViewRepresentable"},{"url":"#coordinator","title":"Coordinator"},{"url":"#trending-cell","title":"Trending Cell"},{"url":"#popular-cell","title":"Popular Cell"},{"url":"#actor-cell","title":"Actor Cell"},{"url":"#upcoming-cell","title":"Upcoming Cell"},{"url":"#section-header-view","title":"Section Header View"},{"url":"#uicollectionviewcompositionallayout","title":"UICollectionViewCompositionalLayout"},{"url":"#uicollectionviewdatasource","title":"UICollectionViewDataSource"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T17:46:42.919Z","isNewLayout":null,"previews":null,"slug":"movie-booking-app-part-5","title":"Real data with the combine framework","updatedDate":"2020-02-02T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZkT0WkZ5lDpk8fmtHza8TaincEu6Hd2NnXy","type":"zip","id":"f6d74e3e-750c-59fb-b1d1-155802572a59","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, \"To get started, you will need the finished part 4 project. If you are not subscribed, get it \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://mailchi.mp/6af910e7c699/e4pdkijada\"\n  }), \"here\"), \", otherwise, check your email inbox. I\\u2019d recommend you to follow along in order to understand what\\u2019s going on.  If you haven\\u2019t read previous parts, I\\u2019d also recommend \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"liquidcoder.com/tag/moviebookingapp/\"\n  }), \"you read them here\"), \" before reading this. However you can still read the first half of this tutorial if you are only interested in the combine framework.\"), mdx(\"h2\", null, \"The Movie Database\"), mdx(\"p\", null, \"Let\\u2019s talk a little bit about the movie database. To perform http requests against the movie database API, you must have an api key, and you will get this key for absolutely free by creating a free account \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.themoviedb.org/account/signup\"\n  }), \"here\"), \". Login into your account, and  click the profile picture located at you top right corner.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1580564271/movie%20store/fl3mgsg8pqvdvv1heszo.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Then click setting in the pop up menu.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1580564271/movie%20store/d0n1vx5wqopb0gpi25r5.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"And last,  click \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"API\"), \" located to your left. Copy the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"API Key (v3 auth)\"), \" value. This is your private key, so make sure you keep it safe, and of course private.\\nYou can also click the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"API\"), \" link in the navigation bar to read the documentation.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Tip:\"), \" Never keep API Keys or any sensitive value in plain text  in your code, make sure to obfuscate them, and/or put them somewhere same. \")), mdx(\"p\", null, \"With that out of the way, let us begin, shall we? We will start by creating a couple of files which in turn will cause lots of compiler errors, but fear not we will fix those as we go.\"), mdx(\"h2\", null, \"The Movie Model\"), mdx(\"p\", null, \"The old movie model will change based on the definition of the json payload we will fetch from TMDB, it will be totally different from what we had before. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Models\"), \" folder, create a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Movie.swift\"), \", and put the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\n\\nstruct TMDBResult: Codable {\\n    let page:Int\\n    let totalPages:Int\\n    let results:[Movie]\\n}\\n\\n\\nstruct Genre: Codable{\\n    let id:Int\\n    let name:String\\n}\\n\\nstruct ProductionCompany: Codable {\\n    let name: String\\n}\\n\\n\\nstruct Movie: Codable {\\n \\n    let id:Int?\\n    let title:String?\\n    let releaseDate:String?\\n    let overview:String?\\n    let popularity:CGFloat?\\n    let genres: [Genre]?\\n    let voteAverage:CGFloat?\\n    let originalLanguage: String?\\n    let posterPath:String?\\n    let backdropPath:String?\\n    let voteCount:Int?\\n    let status:String?\\n    let runtime,revenue:Int?\\n    let budget: Int?\\n    let productionCompanies: [ProductionCompany]?\\n\\n    \\n    static var `default`: Movie {\\n        Movie(id: 0, title: \\\"\\\", releaseDate: \\\"\\\", overview: \\\"\\\", popularity: 0, genres: [], voteAverage: 0, originalLanguage: \\\"\\\", posterPath: \\\"\\\", backdropPath: \\\"\\\", voteCount: 0, status: \\\"\\\", runtime: 0, revenue: 0, budget: 0, productionCompanies: [])\\n    }\\n    \\n}\\n\\n\")), mdx(\"p\", null, \"And yes, we have errors. The problem here is that we already have a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Movie\"), \" struct, so now I want you to completely remove/delete the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieBundle.swift\"), \". That fixed these errors but created several others, I count over 40 errors. Woohoo!!! However, this was expected, right?. What you need to do right now is to act like there are no errors because they will not prevent us to do what we are about to do.\"), mdx(\"p\", null, \"While you still in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Models\"), \" folder, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Ticket\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct Ticket: Identifiable {\\n    var id: UUID\\n    var movie: MovieViewModel\\n    var date: TicketDate\\n    var hour: String\\n    \\n    static var `default`: Ticket{\\n        .init(id: UUID(), movie: MovieViewModel.default, date: TicketDate.default, hour: \\\"\\\")\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The only modification we made here is just using the new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Movie\"), \" struct and make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Ticket\"), \" struct non-generic. However, We keep adding on the errors, fantastic! \"), mdx(\"h2\", null, \"The Movie Database  URLs\"), mdx(\"p\", null, \"For simplicity purposes, we will extend the built-in URL struct, and add our own variables and helper methods that we will use throughout the entire app.\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"URLExt.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\n\\nextension URL{\\n    static private let baseImageUrl = \\\"https://image.tmdb.org/t/p/\\\"\\n    static private let backdropSize = \\\"w780\\\"\\n    static private let posterSize = \\\"w342\\\"\\n    \\n    static private  let apiKey = \\\"YOUR_API_KEY\\\"\\n    static private  let baseUrl = \\\"https://api.themoviedb.org/3/movie\\\"\\n    \\n    \\n    static func movies(for section: HomeSection, page: Int) -> URL{\\n        URL(string: \\\"\\\\(baseUrl)/\\\\(section.rawValue.replacingOccurrences(of: \\\" \\\", with: \\\"_\\\").lowercased() )?api_key=\\\\(apiKey)&language=en-US&page=\\\\(page)\\\")!\\n    }\\n    \\n    \\n    static var topRated: URL{\\n        get{\\n            \\n            URL(string: \\\"\\\\(baseUrl)/top_rated?api_key=\\\\(apiKey)&language=en-US&page=1\\\")!\\n        }\\n    }\\n    \\n    \\n    static var upcoming: URL{\\n         get{\\n              URL(string: \\\"\\\\(baseUrl)/upcoming?api_key=\\\\(apiKey)&language=en-US&page=1\\\")!\\n         }\\n     }\\n     \\n     static var popular: URL{\\n         get{\\n            return  URL(string: \\\"\\\\(baseUrl)/popular?api_key=\\\\(apiKey)&language=en-US&page=1\\\")!\\n         }\\n     }\\n     \\n     static var nowPlaying: URL{\\n         get{\\n             URL(string: \\\"\\\\(baseUrl)/now_playing?api_key=\\\\(apiKey)&language=en-US&page=1\\\")!\\n         }\\n     }\\n     \\n     static func movie(with id: Int) -> URL{\\n         return URL(string: \\\"\\\\(baseUrl)/\\\\(id)?api_key=\\\\(apiKey)&language=en-US&page=1\\\")!\\n     }\\n    \\n}\\n\\n\")), mdx(\"p\", null, \"The above extension is meant to make our lives easier, but it\\u2019s not a good practice.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Tip:\"), \" You should always keep things like URLs and other configs in a place where they can easily be configured for different types of environments naming development, staging, UAT and production. This way your app can use the right configs for each environment.\")), mdx(\"h2\", null, \"Web Service\"), mdx(\"p\", null, \"Like I said earlier, we will use the Combine framework for our simple networking layer simply because it\\u2019s great. Once you get the hang of it, you will never go back to using the callback hell method again (the old way). The Combine framework is a declarative Swift API for processing values over time according to apple. Enough with the pep-talk, let\\u2019s write some code.\"), mdx(\"p\", null, \"In your base folder, create a new folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Networking\"), \", and inside it add a file called \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"WebService.swift\"), \".  Inside that file, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport Foundation\\nimport Combine\\n\\n\\nstruct WebService {\\n\\n\\n}\\n\")), mdx(\"p\", null, \"Don\\u2019t forget to import Combine.\"), mdx(\"h2\", null, \"Configuration\"), mdx(\"p\", null, \"Add the following code inside the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  private var decoder: JSONDecoder = {\\n      let decoder = JSONDecoder()\\n       decoder.keyDecodingStrategy = .convertFromSnakeCase          return decoder\\n  }()\\n\")), mdx(\"p\", null, \"The above code creates a decoder that we will use to parse the json payload from the movie database API, the payload keys will be camel-case that\\u2019s why we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".keyDecodingStrategy\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".convertFromSnakeCase\\t\"), \".\"), mdx(\"p\", null, \"Below that code , add this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" private var session: URLSession = {\\n       let config = URLSessionConfiguration.default\\n        config.urlCache = URLCache.shared\\n        config.waitsForConnectivity = true\\n        config.requestCachePolicy = .returnCacheDataElseLoad\\n        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)\\n    }()\\n\")), mdx(\"p\", null, \"Here we create our own \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"URLSession\"), \" , here is the explanation: \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We are using the built-in shared cache, although you can totally configure it to meet your needs, this \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"shared\"), \" one is enough for this tutorial.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Setting  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"waitForConnectivity\"), \" to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"true\")), \" does what the name says, it checks whether the device is connected to the internet , then loads the data, otherwise, it will wait until the device receive the connection. \"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Setting the cache policy to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".returnCacheDataElseLoad\"), \" means we always check if the data is cached for a particular url exists, otherwise, we fetch it and cache it, and it will never be fetched again as long as the cached version is still available. This behaviour is good if you know that the data from the web API will never change or you are in development mode and don\\u2019t want to fetch the data over and over. Even for this particular app, it\\u2019s not a good option as we are sure that movies are added, changed and/or removed on-a-regular-basis. Like I said you can configure this to meet your back-end behaviour.\")), mdx(\"h2\", null, \"The Combine framework\"), mdx(\"p\", null, \"Below the above code, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     private func createPublisher<T: Codable>(for url: URL) -> AnyPublisher<T, Error>{\\n        return session.dataTaskPublisher(for: url)\\n                       .map({$0.data})\\n                       .decode(type: T.self, decoder: decoder)\\n                        .eraseToAnyPublisher()\\n    }\\n\")), mdx(\"p\", null, \"Let\\u2019s break it down:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First of all, the method is generic, we will reuse that piece of code in a couple of places. The generic parameter must conform to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"codable\"), \" protocol.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The method returns \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyPublisher\"), \". This \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyPublisher\"), \" type is a special type that wraps another Publisher in order to hide its complex signature or definition. Usually, you will find that some Publishers have weird, hard-to-read signatures, so  the above type just makes it simple for us mere mortals to understand.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We kick -off the request with this line  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"session.dataTaskPublisher(for: url)\"), \". Before combine, and if you still don\\u2019t want to use combine, you can use the alternative  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"session.dataTask\"), \" which is callback based. However, we want to use publishers.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The output of the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"session.dataTaskPublisher\"), \" is a tuple of type \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"(data: Data, response: URLResponse)\"), \", so we map through it, and we just retrieve the info that we are interested in for this tutorial. The response is also very important as it would help you return appropriate errors based on the response code if the request failed. In that case using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".tryMap\"), \" operator instead of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".map\"), \" is better. By the way \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".map, .decode,\\u2026\"), \" are called operators in combine.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".decode\"), \" just does what it says, decode the data fetched into  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"TMDBResult\"), \" type, we also use the decoder we defined earlier.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".eraseToAnyPublisher()\"), \" just wraps the returned publisher from \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".decode\"), \" to a simple \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"\\xA0AnyPublisher<TMDBResult, Error>\"), \".\")), mdx(\"p\", null, \"Let\\u2019s now create the method that will return the  movies for the home screen:\\nBelow the above code , add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"      \\n    func getSectionsPublisher() -> AnyPublisher<(TMDBResult, TMDBResult, TMDBResult, TMDBResult), Error>{\\n        Publishers.Zip4(createPublisher(for: .topRated),\\n                        createPublisher(for: .upcoming),\\n                        createPublisher(for: .popular),\\n                        createPublisher(for: .nowPlaying)) // Zip all 4 publishers to run them in parallel\\n                        .eraseToAnyPublisher() // Erase the complex publisher to a simple AnyPublisher\\n    }\\n    \\n\")), mdx(\"p\", null, \"Yep, you read it right\\u2026 there\\u2019s a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"zip4\"), \" operator in combine, what it does is it zips 4 publishers together into a single one.  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Zip4\"), \" will wait until all of the data from all 4 publishers are available before forwarding the 4 values. We also erase to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyPublisher\"), \" here.\"), mdx(\"p\", null, \"After that, add these last 2 methods below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     func getPaginatedPublisher(for section: HomeSection, page: Int) -> AnyPublisher<TMDBResult, Error> {\\n        let url =  URL.movies(for: section, page: page)\\n        return createPublisher(for: url)\\n    }\\n    \\n    func getMovieDetailPublisher(for id: Int) -> AnyPublisher<Movie, Error> {\\n        createPublisher(for: .movie(with: id))\\n    }\\u2028\\n\")), mdx(\"p\", null, \"The method returns a paginated publisher, because we will also cover pagination and the last one return a movie detail publisher.\"), mdx(\"h2\", null, \"View Models\"), mdx(\"p\", null, \"The ViewModel is what the UI will display. In fact, in the MVVM pattern, the UI should never know about the models. View models are links between models and views. \"), mdx(\"p\", null, \"In your \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ViewModels\"), \" folder, replace everything with  the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nenum HomeSection: String, CaseIterable {\\n    case NowPlaying = \\\"Now playing\\\"\\n    case Popular\\n    case Upcoming\\n    case TopRated = \\\"Top rated\\\"\\n}\\n\\nstruct MovieViewModel: Identifiable {\\n    \\n     var id:Int\\n     var title:String\\n     var releaseDate:String\\n     var overview:String\\n     var popularity:CGFloat\\n     var genres:[String]\\n     var voteAverage:Double = 0\\n     var originalLanguage: String\\n     var posterUrl:URL\\n     var backdropUrl:URL\\n     var runtime: String\\n     var productionCompany: String\\n    \\n     private let baseImageUrl = \\\"https://image.tmdb.org/t/p/\\\"\\n     private let backdropSize = \\\"w780\\\"\\n     private let posterSize = \\\"w342\\\"\\n        \\n    \\n    static  var `default` : MovieViewModel {\\n        get{\\n            MovieViewModel(movie: Movie(id: 0, title: \\\"\\\", releaseDate: \\\"\\\", overview: \\\"\\\", popularity: 0, genres: [], voteAverage: 0, originalLanguage: \\\"\\\", posterPath: \\\"\\\", backdropPath: \\\"\\\", voteCount: 0, status: \\\"\\\", runtime: 0, revenue: 0, budget: 0, productionCompanies: []) )\\n        }\\n    }\\n    \\n    init(movie: Movie) {\\n        self.id = movie.id!\\n        self.title = movie.title ?? \\\"N/A\\\"\\n        self.releaseDate = movie.releaseDate ?? \\\"No date\\\"\\n        self.overview = movie.overview ?? \\\"No overview\\\"\\n        self.popularity = movie.popularity ?? 0\\n        self.genres = movie.genres?.map({$0.name}) ?? []\\n        self.originalLanguage = movie.originalLanguage ?? \\\"N/A\\\"\\n        self.backdropUrl = MovieViewModel.backdropImageUrl(with: movie.backdropPath ?? \\\"\\\", baseUrl: self.baseImageUrl, size: backdropSize)\\n        self.posterUrl = MovieViewModel.posterImageUrl(with: movie.posterPath ?? \\\"\\\", baseUrl: baseImageUrl, size: posterSize)\\n        self.runtime = MovieViewModel.formatTime(from: movie.runtime ?? 0)\\n        self.productionCompany = MovieViewModel.productionCompany(movie: movie)\\n        \\n        if let avarage = movie.voteAverage, avarage > 0 {\\n            voteAverage = Double(avarage) / 2\\n        }\\n    }\\n    \\n    \\n    static private func posterImageUrl(with path: String, baseUrl: String, size: String) -> URL {\\n        if let url = URL(string: \\\"\\\\(baseUrl)\\\\(size)\\\\(path)\\\"){\\n            return url\\n        }\\n        \\n        return URL(string: \\\"https://via.placeholder.com/150/0000FF/808080?Text=No&image&available\\\")!\\n    }\\n    \\n    static private func backdropImageUrl(with path: String, baseUrl: String, size: String) -> URL {\\n        if let url = URL(string: \\\"\\\\(baseUrl)\\\\(size)\\\\(path)\\\"){\\n            return url\\n        }\\n        \\n        return URL(string: \\\"https://via.placeholder.com/150/0000FF/808080?Text=No&image&available\\\")!\\n    }\\n       \\n       \\n    static private func productionCompany(movie: Movie) -> String {\\n        \\n        if let prodCompanies = movie.productionCompanies, !prodCompanies.isEmpty {\\n            return prodCompanies.first?.name ?? \\\"N/A\\\"\\n        }\\n        \\n        return \\\"N/A\\\"\\n    }\\n    \\n    \\n    static private func formatTime(from runtime: Int)->String{\\n        if runtime == 0 {\\n            return \\\"00h:00min\\\"\\n        }\\n           let hour = runtime / 60\\n           let min = runtime % 60\\n           return \\\"\\\\(hour)h : \\\\(min)min\\\"\\n       }\\n}\\n\\n\\n\")), mdx(\"p\", null, \"The above code does what a typical view model should do, it convert the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Movie\"), \" model into something that views will easily consume hence the formatting and helper methods.\"), mdx(\"p\", null, \"In the same folder, create a swift file named  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieListingViewModel.swift\"), \".\\nInside the file, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport Combine\\n\\nclass MovieListViewModel: ObservableObject {\\n    private var webService = WebService()\\n    private var cancellableSet: Set<AnyCancellable> = []\\n    \\n    @Published var sectionMovies =  [HomeSection: [MovieViewModel]]()\\n    @Published var movie = MovieViewModel.default\\n    @Published var paginatedMovies = [MovieViewModel]()\\n}\\n\")), mdx(\"p\", null, \"In the above code, we:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"create a new instance of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"WebService\"), \".\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"create a set of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyCancellable\"), \" to store all \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyCancellable\"), \" objects  returned by the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".sink\"), \" operator that you will learn about shortly. Failure to store \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Cancellable\"), \" objects will result in publishers being cancelled prematurely.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"then create published objects that views will utilise.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func getSectionMovies()  {\\n        webService\\n            .getSectionsPublisher()\\n            .receive(on: DispatchQueue.main)\\n            .sink(receiveCompletion: { status in\\n                switch status {\\n                case .finished:\\n                    break\\n                case .failure(let error):\\n                    print(error)\\n                    break\\n                }\\n            }) { (topRated,upcoming, popular, nowPlaying) in\\n                self.sectionMovies[.TopRated] = topRated.results.map(MovieViewModel.init)\\n                self.sectionMovies[.Upcoming] = upcoming.results.map(MovieViewModel.init)\\n                self.sectionMovies[.Popular] = popular.results.map(MovieViewModel.init)\\n                self.sectionMovies[.NowPlaying] = nowPlaying.results.map(MovieViewModel.init)\\n        }.store(in: &self.cancellableSet)\\n    }\\n\")), mdx(\"p\", null, \"Here we do this:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"we receive the publisher\\u2019s result on the main thread because we cannot set  published objects on a background thread.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We then subscribe to the publisher using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"sink\"), \" operator. For simplicity, we don\\u2019t handle  errors appropriately in this tutorial. In the second closure, we set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"sectionMovies\"), \" map above.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Last we store the cancellable objects in the set.\")), mdx(\"p\", null, \"Add the following method below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func getPaginatedMovies(for section: HomeSection, page: Int)  {\\n        webService.getPaginatedPublisher(for: section, page: page)\\n            .receive(on: DispatchQueue.main)\\n            .sink(receiveCompletion: { status in\\n                switch status {\\n                case .finished:\\n                    break\\n                case .failure(let error):\\n                    print(error)\\n                    break\\n                }\\n            }) { page in\\n                self.paginatedMovies.append(contentsOf:  page.results.map(MovieViewModel.init))\\n        }.store(in: &self.cancellableSet)\\n    }\\n\")), mdx(\"p\", null, \"This method does the same thing as the one above, but append the results to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"paginatedMovies\"), \" array.\"), mdx(\"p\", null, \"And last, add this below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func getMovieDetail(id: Int)  {\\n        webService.getMovieDetailPublisher(for: id)\\n            .receive(on: DispatchQueue.main)\\n            .sink(receiveCompletion: { status in\\n               switch status {\\n                case .finished:\\n                    break\\n                case .failure(let error):\\n                    print(error)\\n                    break\\n                }\\n            }) { movie in\\n                self.movie = MovieViewModel(movie: movie)\\n        }.store(in: &self.cancellableSet)\\n    }\\n\")), mdx(\"p\", null, \"This one set the single movie.\"), mdx(\"h2\", null, \"KingFisher with Swift Package Manager.\"), mdx(\"p\", null, \"We will use the swift package manager to integrate the kingfisher package into the project. We will use kingfisher to download and cache images.\"), mdx(\"p\", null, \"Follow this to install kingfisher using the swift package manager:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Select File\\xA0\"), \">\", \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Swift Packages\"), \" \", \">\", \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Add Package Dependency\"), \". Enter \", mdx(\"em\", {\n    parentName: \"p\"\n  }, mdx(\"a\", _extends({\n    parentName: \"em\"\n  }, {\n    \"href\": \"https://github.com/onevcat/Kingfisher.git\"\n  }), \"https://github.com/onevcat/Kingfisher.git\"), \"\\xA0\"), \" in the \\\"Choose Package Repository\\\" dialog.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"In the next page, specify the version resolving rule as \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\\"Up to Next Major\\\"\"), \" with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\u201C#.#.#\\u201D\"), \" as its earliest version.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1580564197/movie%20store/fmx9heg63iwxxmpnro8o.png\",\n    \"alt\": null\n  })))), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"After Xcode has finished checking out the source and resolving the version, you can choose the \\\"Kingfisher\\\" and \\u201CKingfisherSwiftUI\\u201D libraries and add them to your app target.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1580564197/movie%20store/hoedzb0wueymrqkfityp.png\",\n    \"alt\": null\n  }))))), mdx(\"h2\", null, \"Refactoring the old view code to use the new view model\"), mdx(\"p\", null, \"This portion can be done by anybody. I won\\u2019t go into much explanation, it\\u2019s just replacing the old model by the new one. So let\\u2019s get started:\"), mdx(\"h2\", null, \"MovieCollectionView\"), mdx(\"p\", null, \"Replace \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allItems\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var allItems: [HomeSection: [MovieViewModel]]\\n\")), mdx(\"p\", null, \"Then replace the content of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cellForItemAt\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     switch indexPath.section {\\n            case 0:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TrendingCell.reuseId, for: indexPath) as? TrendingCell{\\n                    cell.movie = parent.allItems[.NowPlaying]?[indexPath.item]\\n                    return cell\\n                }\\n            case 1:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PopularCell.reuseId, for: indexPath) as? PopularCell{\\n                    cell.movie = parent.allItems[.Popular]?[indexPath.item]\\n                    return cell\\n                }\\n                \\n            case 2:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UpcomingCell.reuseId, for: indexPath) as? UpcomingCell{\\n                    cell.movie = parent.allItems[.Upcoming]?[indexPath.item]\\n                    return cell\\n                }\\n            default:\\n                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PopularCell.reuseId, for: indexPath) as? PopularCell{\\n                    cell.movie = parent.allItems[.TopRated]?[indexPath.item]\\n                    return cell\\n                }\\n            }\\n            return UICollectionViewCell()\\n\")), mdx(\"h2\", null, \"TrendingCell\"), mdx(\"p\", null, \"In TrendingCell replace the following code: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var trending: Trending?{\\n        didSet{\\n            if let trending = self.trending {\\n                imageView.image = UIImage(named: \\\"\\\\(trending.image)_land.jpg\\\")\\n                titleLabel.text = trending.title\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"With this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  var movie: MovieViewModel?{\\n        didSet{\\n            if let movie = self.movie {\\n                // This line use kingFisher to download, set the image to the image view and caching it at the same time\\n                imageView.kf.setImage(with: movie.backdropUrl)\\n                titleLabel.text = movie.title\\n            }\\n        }\\n    }\\n    \\n\")), mdx(\"p\", null, \"And add  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"import KingfisherSwiftUI\"), \" below UIKit import statement.\"), mdx(\"p\", null, \"Do the same thing for \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PopularCell\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UpcomingCell\"), \", but for these celll, use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"movie.posterUrl\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"movie.backdropUrl\"), \". Delete the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ActorCell.swift\"), \" file and the line that register it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeUIView\"), \" method.\"), mdx(\"h2\", null, \"MovieStoreApp\"), mdx(\"p\", null, \"Replace everything in the  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MovieStoreApp.swift\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\n\\nstruct MovieStoreApp: View {\\n    \\n    @State private var showDetails = false\\n    @State private var selectedIndexPath: IndexPath?\\n    @State private var section: HomeSection = .Popular\\n    @State private var showSheet = false\\n    \\n    @ObservedObject var model = MovieListingViewModel()\\n    \\n    var body: some View {\\n        \\n        \\n        return NavigationView {\\n            \\n            if model.sectionMovies.isEmpty{\\n                LoadingView().frame(width: 50, height: 50)\\n            } else {\\n                createCollectionView()\\n                    .sheet(isPresented: self.$showSheet) {\\n                        if self.selectedIndexPath == nil{\\n                            MovieListView(section: self.section)\\n                        } else {\\n                            SingleMovieView(movieId: self.model.sectionMovies[self.section]?[self.selectedIndexPath!.item].id ?? 0 )\\n                        }\\n                }\\n            }\\n        }.onAppear {\\n            self.model.getSectionMovies()\\n        }\\n    }\\n    \\n    fileprivate func createCollectionView() -> some View {\\n       \\n        return MovieCollectionView(allItems: model.sectionMovies,\\n                                          didSelectItem: { indexPath in\\n                                           self.selectedIndexPath = indexPath\\n                                           self.section = HomeSection.allCases[indexPath.section]\\n                                           self.showSheet.toggle()\\n               },\\n                                          seeAllforSection: { section in\\n                                           self.section = section\\n                                                          self.showSheet.toggle()\\n                                                          self.selectedIndexPath = nil\\n               }).edgesIgnoringSafeArea(.all).navigationBarTitle(\\\"Movies\\\")    }\\n}\\n\\nstruct MovieStoreApp_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MovieStoreApp()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Here is what\\u2019s changed:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We replace the old model with the new one, \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"MovieListingViewModel\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We check whether the data has loaded then we show collectionView, otherwise we show the loading indicator.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onAppear\"), \" block, we fetch the movies. When movies are loaded, the view will reload because we are using an \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ObservableObject\"), \".\")), mdx(\"h2\", null, \"MovieListView\"), mdx(\"p\", null, \"Replace the content in the MovieListView with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct MovieListView: View {\\n    \\n    var section: HomeSection\\n    @ObservedObject var model = MovieListingViewModel()\\n    @State private var page: Int = 1\\n    \\n    var body: some View {\\n        NavigationView {\\n            List{\\n                ForEach(0..<model.paginatedMovies.count, id: \\\\.self) { i in\\n                    MovieListRow(movie: self.model.paginatedMovies[i]).onAppear {\\n                        if i == self.model.paginatedMovies.count - 1{\\n                            self.page += 1\\n                            self.model.getPaginatedMovies(for: self.section, page: self.page)\\n                        }\\n\\n                    }\\n                }\\n            }.navigationBarTitle(section.rawValue)\\n        }.onAppear {\\n            self.model.getPaginatedMovies(for: self.section, page: self.page)\\n        }\\n    }\\n}\\n\\nstruct MovieListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MovieListView(section: .TopRated)\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The code does the following:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First the struct is not generic anymore. We once again get an instance of the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"MovieListingViewModel\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We implement the pagination in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ForEach\"), \" block.\\nWe check if the loaded \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"MovieListRow\"), \" item is the last in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"paginatedMovies\"), \" array, then increase the page count before performing another request for that page. The result will be appended to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"paginatedMovies\"), \" array which will in turn update \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"List\"), \" thanks to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ObservableObject\\xA0\"), \" protocol. \")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"MovieListRow(movie: self.model.paginatedMovies[i]).onAppear {\\n                            if i == self.model.paginatedMovies.count - 1{\\n                                self.page += 1\\n                                self.model.getPaginatedMovies(for: self.section, page: self.page)\\n                            }\\n    \\n                        }\\n\")), mdx(\"p\", null, \"All remaining code do not need any explanation at all as there\\u2019s no major changes. You can just compare the old with new code and you will understand quite easily.\"), mdx(\"h2\", null, \"MovieListRow\"), mdx(\"p\", null, \"Replace the content of this file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct MovieListRow: View {\\n    var movie: MovieViewModel\\n    \\n    fileprivate func createImage() -> some View {\\n        return KFImage(source: .network(movie.posterUrl))\\n        .resizable()\\n        .aspectRatio(contentMode: .fit).cornerRadius(20)\\n    }\\n        \\n    fileprivate func createTitle() -> some View {\\n        return Text(movie.title)\\n        .font(.system(size: 25, weight: .black, design: .rounded))\\n        .foregroundColor(Color.white)\\n    }\\n    \\n    var body: some View {    \\n        return ZStack(alignment: .bottom) {\\n            createImage()\\n            \\n            VStack(alignment: .leading) {\\n                createTitle()\\n                LineRatingView(value: movie.voteAverage)\\n            }.frame(maxWidth: .infinity, alignment: .leading)\\n                .padding()\\n                .background(LinearGradient(gradient: Gradient(colors: [Color.black, Color.clear]) , startPoint: .bottom , endPoint: .top)).cornerRadius(20)\\n                .shadow(radius: 10)\\n        \\n        }.padding(.vertical)\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"SingleMovieView\"), mdx(\"p\", null, \"Replace the content of that file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct SingleMovieView: View {\\n    \\n    var movieId: Int = -1\\n    \\n    @ObservedObject var model = MovieListViewModel()\\n    \\n    var body: some View {\\n        ScrollView(showsIndicators: false) {\\n                    VStack(alignment: .leading) {\\n                        createPosterImage()\\n                        MovieDetailView(movie: self.model.movie)\\n                    }\\n                }.edgesIgnoringSafeArea(.top)\\n                .onAppear {\\n                    self.model.getMovieDetail(id: self.movieId)\\n                }\\n        }\\n    \\n    \\n    \\n       fileprivate func createPosterImage() -> some View {\\n        return KFImage(source: .network(model.movie.posterUrl)).resizable()\\n           .aspectRatio(contentMode: .fit)\\n       }\\n}\\n\\n\")), mdx(\"h2\", null, \"SingleMovieView\"), mdx(\"p\", null, \"Replace the content of that file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct MovieDetailView: View {\\n     var movie: MovieViewModel\\n        \\n   @State private var showSeats: Bool = false\\n        \\n        var body: some View {            \\n            \\n           return VStack(alignment: .leading) {\\n                createTitle()\\n            LineRatingView(value: movie.voteAverage)\\n                createGenreList()\\n                HStack {\\n                    Text(self.movie.releaseDate).foregroundColor(Color.gray)\\n                    Spacer()\\n                    Text(self.movie.runtime ).foregroundColor(Color.gray)\\n                }.padding(.vertical)\\n                createDescription()\\n                createChooseSeatButton()                \\n            }.padding(.horizontal).padding(.bottom, 20)\\n        }\\n    \\n    fileprivate func createTitle() -> some View {\\n        return Text(self.movie.title)\\n        .font(.system(size: 35, weight : .black, design: .rounded))\\n        .layoutPriority(1)\\n        .multilineTextAlignment(.leading)\\n        .lineLimit(nil)\\n    }\\n    \\n    fileprivate func createGenreList() -> some View {\\n        \\n        return ScrollView(.horizontal, showsIndicators: false) {\\n            HStack{\\n                ForEach(self.movie.genres, id: \\\\.self){ genre in\\n                    Text(genre)\\n                        .bold()\\n                        .padding(5)\\n                        .background(Color.lightGray)\\n                        .cornerRadius(10)\\n                        .foregroundColor(Color.gray)\\n\\n                }\\n            }\\n        }\\n    }\\n    \\n    fileprivate func createDescription() -> some View {\\n        return Text(self.movie.overview).lineLimit(nil).font(.body)\\n    }\\n    \\n    fileprivate func createChooseSeatButton() -> some View {\\n        return LCButton(text: \\\"Choose seats\\\") {\\n            self.showSeats.toggle()\\n        }.sheet(isPresented: self.$showSeats) {\\n            SeatsChoiceView(movie: self.movie)\\n        }.padding(.vertical)\\n    }\\n}\\n\")), mdx(\"h2\", null, \"SeatsChoiceView\"), mdx(\"p\", null, \"Replace the content of the file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct SeatsChoiceView: View {\\n    var movie: MovieViewModel\\n    \\n    @State private var selectedSeats: [Seat] = []\\n    @State private var showBasket: Bool = false\\n    @State private var date: TicketDate = TicketDate.default\\n    @State private var hour: String = \\\"\\\"\\n    @State private var showPopup = false\\n    \\n    var body: some View {\\n        NavigationView {\\n            ScrollView(showsIndicators: false ) {\\n                VStack(spacing: 0) {\\n                    TheatreView(selectedSeats: self.$selectedSeats).padding(.top, 20)\\n                    DateTimeView(date: self.$date, hour: self.$hour)\\n                    LCButton(text: \\\"Continue\\\", action: {\\n                        self.showBasket = self.validateInputs()\\n                        withAnimation {\\n                            self.showPopup = !self.validateInputs()\\n                        }\\n                    }).sheet(isPresented: self.$showBasket) {\\n                        BasketView(ticket: Ticket(id: UUID(), movie: self.movie, date: self.date, hour: self.hour) , selectedSeats: self.selectedSeats\\n                        )\\n                    }.padding()\\n                }.navigationBarTitle(\\\"Choose seats\\\", displayMode: .large)\\n                    .frame(maxHeight: .infinity)\\n                    .accentColor(Color.accent)\\n            }\\n        }.blur(radius: self.showPopup ? 10 : 0).overlay(\\n            VStack{\\n                if self.showPopup {\\n                    self.createPopupContent()\\n                } else {\\n                    EmptyView()\\n                }\\n            }.frame(maxWidth: .infinity, maxHeight: .infinity)\\n                .background( self.showPopup ? Color.background.opacity(0.3) : .clear)\\n        )\\n    }\\n    \\n    fileprivate func createPopupContent() -> some View {\\n        VStack {\\n            Text(\\\"Not allowed\\\").font(.system(size: 20, weight: Font.Weight.semibold))\\n            Text(\\\"You need to select at least one seat, a date and hour in order to continue.\\\")\\n                .multilineTextAlignment(.center).frame(maxHeight: .infinity)\\n            LCButton(text: \\\"Let's do that\\\") {\\n                withAnimation {\\n                    self.showPopup.toggle()\\n                }\\n            }\\n        }.frame(width: UIScreen.main.bounds.width * 0.8, height: 200, alignment: .bottom)\\n            .padding()\\n            .background(Color.background.opacity(0.7))\\n            .cornerRadius(20)\\n            .shadow(color: Color.textColor.opacity(0.3), radius: 20, x: 0, y: 10)\\n            .transition(.move(edge: .bottom))\\n    }\\n    \\n    fileprivate func validateInputs() -> Bool {\\n        self.selectedSeats.count > 0\\n        && self.date != TicketDate.default\\n        && !self.hour.isEmpty\\n    }\\n    \\n}\\n\\nstruct ChooseSeatsView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SeatsChoiceView(movie: MovieViewModel.default)\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"Basket View\"), mdx(\"p\", null, \"Replace the content of the file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct BasketView: View {\\n    var ticket: Ticket\\n    var selectedSeats: [Seat]\\n    @State private var showPaymentScreen = false\\n    \\n    \\n    var body: some View {\\n        \\n        return\\n            VStack(spacing: 0) {\\n                ScrollView(.horizontal, showsIndicators: false) {\\n                    HStack {\\n                        ForEach(selectedSeats) { seat in\\n                            GeometryReader { gr in\\n                                self.renderTicket(Ticket(id: self.ticket.id, movie:  self.ticket.movie, date:  self.ticket.date, hour:  self.ticket.hour), seat: seat , angle: gr.frame(in: .global).minX / -10)\\n                            }.frame(width: UIScreen.main.bounds.width)\\n                        }\\n                    }\\n                }\\n                LCButton(text: \\\"Buy\\\", action: {self.showPaymentScreen.toggle()})\\n                    .sheet(isPresented: self.$showPaymentScreen, content: {\\n                        PaymentView()\\n                    })\\n                    .padding(.horizontal)\\n                    .padding(.bottom)\\n        }\\n    }\\n    \\n    func renderTicket(_ ticket: Ticket, seat: Seat, angle: CGFloat) -> some View {\\n        \\n        return TicketView(ticket: ticket, seat: seat)\\n            .rotation3DEffect(Angle(degrees: Double(angle)) , axis: (x: 0, y: 10.0, z: 0))\\n    }\\n}\\n\\nstruct BasketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BasketView(ticket: Ticket.default, selectedSeats: [])\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"TicketView\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct TicketView: View {\\n\\n    var ticket: Ticket\\n    var seat = Seat.default\\n    \\n    var body: some View {\\n      VStack(spacing: 0) {\\n        TopTicketView(ticket: ticket, seat: seat)\\n                      .background(Color.background)\\n                      .clipShape(TicketShape())\\n                  .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)\\n\\n                  DashedSeperator()\\n                      .stroke(Color.gray, style: StrokeStyle(lineWidth: 1,dash: [4,8], dashPhase: 4))\\n                      .frame(height: 0.4)\\n                      .padding(.horizontal)\\n\\n                  BottomTicketView()\\n                      .background(Color(\\\"barcodeBG\\\"))\\n                   .clipShape(TicketShape().rotation(Angle(degrees: 180)))\\n                  .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)\\n\\n      }.padding()\\n    }\\n}\\n\\nstruct TicketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TicketView(ticket: Ticket.default)\\n    }\\n}\\n\\n\")), mdx(\"h2\", null, \"TopTicketView\"), mdx(\"p\", null, \"Replace the content of the file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\nimport KingfisherSwiftUI\\n\\n\\nstruct TopTicketView: View {\\n    \\n    var ticket: Ticket\\n    var seat = Seat.default\\n    \\n    var body: some View {\\n        VStack{\\n            VStack(alignment: .leading) {\\n                Text(ticket.movie.productionCompany) // Change here\\n                    .font(.system(size: 20, weight: .bold))\\n                    .foregroundColor(Color.gray)\\n                Text(ticket.movie.title)\\n                    .font(.system(size: 30, weight: .black))\\n            }.frame(minWidth: 0.0, maxWidth:.infinity, alignment: .leading)\\n                .padding(.top, 30)\\n                .padding(.horizontal)\\n        \\n            KFImage(source: .network(ticket.movie.backdropUrl) )\\n                .resizable().frame(minWidth: 0.0, maxWidth: .infinity)\\n                .scaledToFit()\\n                \\n            HStack{\\n                TicketDetailView(detail1: \\\"SCREEN\\\", detail2: \\\"18\\\", detail3: \\\"PRICE\\\", detail4: \\\"$5.68\\\")\\n                TicketDetailView(detail1: \\\"ROW\\\", detail2: \\\"\\\\(seat.row)\\\", detail3: \\\"DATE\\\", detail4: \\\"\\\\(ticket.date.day)/\\\\(ticket.date.month)/\\\\(ticket.date.year)\\\")\\n                TicketDetailView(detail1: \\\"SEAT\\\", detail2: \\\"\\\\(seat.number)\\\", detail3: \\\"TIME\\\", detail4: ticket.hour)\\n            }.padding(.vertical)\\n        }\\n    }\\n}\\n\\nstruct TopTicketView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        TopTicketView(ticket: Ticket.default)\\n    }\\n}\\n\\n\\n\\n\")), mdx(\"p\", null, \"The last thing is to completely delete the  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ActorListView.swift\\xA0\"), \"  and  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ActorDetailView.swift\"), \" located in the Screens folder and  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ActorListRow.swift\"), \"  located in the Views folder.\"), mdx(\"p\", null, \"Run the app and hopefully everything should work fine. Try out the pagination as well in the see all list.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"And that\\u2019s it, folks. Like I said combine is amazing, it really changes the way we do things in swift mobile development, and pairing it with swift UI feels like paradise on earth. There\\u2019s so much already we can do with the 2 frameworks still brand new, I can\\u2019t imagine what we will accomplish once they mature. Stay  tuned for an amazing project I am preparing for you and many more, please share the article and subscribe for more. HAPPY CODING!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#the-movie-database","title":"The Movie Database"},{"url":"#the-movie-model","title":"The Movie Model"},{"url":"#the-movie-database--urls","title":"The Movie Database  URLs"},{"url":"#web-service","title":"Web Service"},{"url":"#configuration","title":"Configuration"},{"url":"#the-combine-framework","title":"The Combine framework"},{"url":"#view-models","title":"View Models"},{"url":"#kingfisher-with-swift-package-manager","title":"KingFisher with Swift Package Manager."},{"url":"#refactoring-the-old-view-code-to-use-the-new-view-model","title":"Refactoring the old view code to use the new view model"},{"url":"#moviecollectionview","title":"MovieCollectionView"},{"url":"#trendingcell","title":"TrendingCell"},{"url":"#moviestoreapp","title":"MovieStoreApp"},{"url":"#movielistview","title":"MovieListView"},{"url":"#movielistrow","title":"MovieListRow"},{"url":"#singlemovieview","title":"SingleMovieView"},{"url":"#singlemovieview-1","title":"SingleMovieView"},{"url":"#seatschoiceview","title":"SeatsChoiceView"},{"url":"#basket-view","title":"Basket View"},{"url":"#ticketview","title":"TicketView"},{"url":"#topticketview","title":"TopTicketView"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T17:53:51.199Z","isNewLayout":null,"previews":null,"slug":"swiftui-2-0-update","title":"SwiftUI 2.0 Update","updatedDate":"2020-07-17T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ52gNkZgpREMJqUoIhlc1mnR6e78k1VjLQX","type":"zip","id":"a9052f42-b27b-5fac-9d9e-73ddbf893094","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, swift UI 2.0 is out, if you didn't know now you know. So I wanted to update the movie booking app with the new changes. The main component that will be replaced is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UICollectionView\"), \" as there's now a swiftUI alternative. With this change,  we will loose the peekaboo effect, but that's nothing compared to what we will get . Let's jump right into it.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 12 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Catalina or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 14 or greater\")), mdx(\"h2\", null, \"Get started\"), mdx(\"p\", null, \"I want to try a  new format of writing where I will show you the code and the file that contains it,  the preview and last the explanation, just create the file if you don't have it already.\"), mdx(\"p\", null, \"To follow along, you need download the previous project  source code above, and when you finish, try to compare your work with the final project. \"), mdx(\"h2\", null, \"Initial cleanup\"), mdx(\"p\", null, \"We will need to get rid of some unnecessary files that we don't need anymore... So, delete the entire Images folder, then in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views/Cells\"), \" folder add the following swiftUI files:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"NowPlayingView.swift\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"PopularView.swift\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"UpcomingView.swift\"))), mdx(\"p\", null, \"Add the following in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"DynamicContainer.swift\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"em\", {\n    parentName: \"li\"\n  }, \"SectionView.swift\"))), mdx(\"p\", null, \"Now let's start filling them up, shall we?\"), mdx(\"h2\", null, \"Now Playing\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"NowPlayingView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct NowPlayingView: View {\\n    \\n    var movie: MovieViewModel\\n    \\n    var body: some View {\\n        return\\n           KFImage(source: .network(movie.backdropUrl))\\n            .resizable()\\n            .aspectRatio(contentMode: .fit)\\n                .overlay(\\n                    Text(movie.title)\\n                        .font(.title2)\\n                        .bold()\\n                        .foregroundColor(Color.white)\\n                        .frame(maxWidth: .infinity, alignment: .leading)\\n                        .padding(.leading, 10)\\n                        .padding(.bottom, 10)\\n                        .background(\\n                            LinearGradient(gradient: Gradient(colors: [Color.black, Color.clear]), startPoint: .bottom, endPoint: .top)\\n                        ), alignment: .bottom\\n                ).cornerRadius(20)\\n    }\\n}\\n\\nstruct NowPlayingView_Previews: PreviewProvider {\\n    \\n    static var previews: some View {\\n        NowPlayingView(movie: MovieViewModel.default)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NowPlayingView\"), \" will just be an image with a text overlay aligned to the bottom. We've also added a gradient background to the text to make it readable on light background.\"), 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/v1594293248/movie%20store/jkydrory3brobgtffo9w.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1594293248/movie%20store/jkydrory3brobgtffo9w.png\"\n  }))), mdx(\"h2\", null, \"Popular & Top rated\"), mdx(\"p\", null, \"The top rated and popular section will use the same view, I will  call the file \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Popular.swift,\"), \" but feel free to be creative.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Popular.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct PopularView: View {\\n    \\n    var movie: MovieViewModel\\n    \\n    var body: some View {\\n        VStack( alignment: .leading, spacing: 0) {\\n            \\n            KFImage(source: .network(movie.posterUrl)).resizable()\\n             .aspectRatio(contentMode: .fill)\\n                .cornerRadius(20)\\n                .clipped()\\n                .padding(.top, 5)\\n         \\n            \\n            Text(\\\"Star wars: The rise of skywalker\\\")\\n                .font(.headline)\\n                .multilineTextAlignment(.leading)\\n                .frame(maxWidth: .infinity,alignment: .leading)\\n                .padding(.all, 10)\\n        }\\n            \\n    }\\n}\\n\\nstruct PopularView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PopularView(movie:  MovieViewModel.default)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"As you can see, in this view we stack the image and the title vertically.\"), 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/v1594293248/movie%20store/z1llbhmcwk38r4uwpz2q.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1594293248/movie%20store/z1llbhmcwk38r4uwpz2q.png\"\n  }))), mdx(\"h2\", null, \"Upcoming\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"UpcomingView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport KingfisherSwiftUI\\n\\nstruct UpcomingView: View {\\n    \\n    var movie: MovieViewModel\\n    \\n    var body: some View {\\n        HStack{\\n            KFImage(source: .network(movie.backdropUrl)).resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .cornerRadius(10.0)\\n    \\n            VStack(alignment: .leading) {\\n                Text(\\\"Spies in disguise\\\").font(.title).bold()\\n                Text(movie.releaseDate)\\n                                    .foregroundColor(.gray)\\n\\n            }\\n            \\n            Spacer()\\n        }\\n    }\\n}\\n\\nstruct UpcomingView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        UpcomingView(movie: MovieViewModel.default)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"As you can see, in this view we stack the image and the title horizontally.\"), 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/v1594293249/movie%20store/vycdw2zg79eyecdkwg1v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1594293249/movie%20store/vycdw2zg79eyecdkwg1v.png\"\n  }))), mdx(\"h2\", null, \"Dynamic Container\"), mdx(\"p\", null, \"This will be a generic container that we will use to  layout different types of views, in our case we will use only the newly introduced \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyGrid\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyHStack\"), \" , but sky is the limit, one might extend it to include other containers.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"DynamicContainer.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct DynamicContainer<Model, Content>: View where Content: View, Model: Identifiable {\\n    \\n    enum ContainerType {\\n        case stack, grid\\n    }\\n\\n    \\n    var type: ContainerType\\n    var data: [Model]\\n    var content: (Model) -> Content\\n    \\n    \\n    private let gridLayout = [\\n        GridItem(.flexible(minimum: 100), spacing: 10, alignment: .center),\\n        GridItem(.flexible(minimum: 100), spacing: 10, alignment: .center)\\n    ]\\n    \\n    \\n    init(data: [Model]?, type: ContainerType = .stack, @ViewBuilder content: @escaping (Model) -> Content) {\\n        self.data = data ?? []\\n        self.type = type\\n        self.content = content\\n    }\\n    \\n    var body: some View {\\n        \\n        switch type {\\n        case .stack:\\n            return AnyView(\\n                    LazyHStack(spacing: 20) {\\n                        ForEach(data) { value in\\n                            self.content(value)\\n                        }\\n                }.padding(.horizontal, 20)\\n            )\\n        case .grid:\\n            return AnyView(LazyHGrid(rows: gridLayout, spacing: 0) {\\n                ForEach(data) { value in\\n                    self.content(value)\\n                }\\n            })\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Explanation\"))), mdx(\"p\", null, \"That's a big chunk of code, let's break it down. The struct is generic where the first generic parameter must conform to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Identifiable\"), \" protocol and the second one must also conform to the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"View\"), \"  protocol. We declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContainerType\"), \" enum inside  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DynamicContainer\"), \" because it will only be used in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DynamicContainer\"), \".\"), mdx(\"p\", null, \"We then declare all the properties that we want to pass in via the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method. The first one is just the type of the container to use, the second one is an array of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Model\"), \" type and the last one is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"content\"), \" ,which in this case, is a closure taking in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Model\"), \" and spitting out a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"View\"), \".\"), mdx(\"p\", null, \"We've also created the a private \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridLayout\"), \" property which is not ideal, but in this example we just need one layout, so it will not kill us to just declare it above.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" block, we switch on the type of container (because we can now use switch statement in here) and return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyView\"), \" passing in the appropriate type of container.\"), mdx(\"h2\", null, \"Section View\"), mdx(\"p\", null, \"The section view will be used to layout each section.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SectionView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct SectionView: View {\\n    \\n    var sectionType: HomeSection = .NowPlaying\\n    var allItems: [HomeSection: [MovieViewModel]]\\n    @Binding var seeSection: HomeSection?\\n    @Binding var selectedMovie: MovieViewModel?\\n    \\n    var data = [Int]()\\n    \\n    \\n    var body: some View {\\n        \\n        \\n        return VStack(spacing: 5) {\\n            HStack {\\n                Text(sectionType.rawValue)\\n                    .font(.title2)\\n                    .bold()\\n\\n                Spacer()\\n\\n                Button(action: {\\n                    seeSection = sectionType\\n                }, label: {\\n                    Text(\\\"See all\\\")\\n                })\\n            }.padding(.horizontal, 20)\\n            \\n            \\n            ScrollView(.horizontal, showsIndicators: false) {\\n                \\n                switch sectionType {\\n                case .NowPlaying:\\n                    DynamicContainer(data: allItems[sectionType]) { value in\\n                        NowPlayingView(movie: value)\\n                            .frame(width: UIScreen.main.bounds.width - 40)\\n                            .onTapGesture {\\n                                self.selectedMovie = value\\n                            }\\n                    }.frame(height: 220)\\n                    \\n                case .Popular:\\n                    DynamicContainer(data: allItems[sectionType]) { value in\\n                        PopularView(movie: value)\\n                            .frame(width: UIScreen.main.bounds.width / 2 - 40)\\n                            .onTapGesture {\\n                                self.selectedMovie = value\\n                            }\\n                        \\n                    }\\n                    \\n                case .Upcoming:\\n                    DynamicContainer(data: allItems[sectionType], type: .grid) { value in\\n                        UpcomingView(movie: value)\\n                        .padding(.horizontal, 20)\\n                        .frame(width: UIScreen.main.bounds.width)\\n                            .onTapGesture {\\n                                self.selectedMovie = value\\n                            }\\n                    }.padding(.top, 5)\\n\\n                case .TopRated:\\n                    DynamicContainer(data: allItems[sectionType]) { value in\\n                        PopularView(movie: value)\\n                            .frame(width: UIScreen.main.bounds.width / 2 - 40)\\n                            .onTapGesture {\\n                                self.selectedMovie = value\\n                            }\\n                            \\n                    }\\n                }\\n            }\\n            \\n        }\\n    }\\n}\\n\\nstruct SectionView_Previews: PreviewProvider {\\n    \\n    static var previews: some View {\\n        SectionView(allItems: [:], seeSection: .constant(.NowPlaying), selectedMovie: .constant(MovieViewModel.default))\\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 might seem like a lot, but it's just a bunch of repetition in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"switch\"), \" statement with some minor differences.\"), mdx(\"p\", null, \"The only thing to explain is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DynamicContainer\"), \"  call, we set the data from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allItems\"), \" map then in the content closure, we create the view that will act as a cell.\"), mdx(\"h2\", null, \"Update the MovieStoreApp\"), mdx(\"p\", null, \"Add the following declaration to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State private var selectedMovie: MovieViewModel?\\n\")), mdx(\"p\", null, \"Then add the following function below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createCollectionView\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createSections() -> some View {\\n     \\n        ScrollView{\\n            VStack(spacing: 20) {\\n                SectionView(sectionType: .NowPlaying, allItems: model.sectionMovies, seeSection: $section, selectedMovie: $selectedMovie)\\n                SectionView(sectionType: .Popular, allItems: model.sectionMovies, seeSection: $section, selectedMovie: $selectedMovie)\\n                SectionView(sectionType: .Upcoming, allItems: model.sectionMovies, seeSection: $section, selectedMovie: $selectedMovie)\\n                SectionView(sectionType: .TopRated, allItems: model.sectionMovies, seeSection: $section, selectedMovie: $selectedMovie)\\n            }.padding(.top, 20)\\n        }.navigationTitle(Text(\\\"Movies\\\"))\\n        \\n    }\\n\")), mdx(\"p\", null, \"Now replace the entire \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"else\"), \" block with the following\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"createSections()\\n        .sheet(isPresented: .constant(self.section != nil || selectedMovie != nil), onDismiss: {\\n            selectedMovie = nil\\n               section = nil\\n        }) {\\n    selectedMovie.map { SingleMovieView(movieId: $0.id) }\\n    section.map { MovieListView(section: $0) }\\n}\\n\")), mdx(\"p\", null, \"Now delete these lines:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State private var showDetails = false\\n@State private var selectedIndexPath: IndexPath?\\n@State private var showSheet = false\\n\")), mdx(\"p\", null, \"And the   \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createCollectionView\"), \" function. You should also delete the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"UIKit\"), \" folder as well as the files inside it.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That's it folks, the small update that I wanted to make... \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVStack\"), \" , \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyHStack\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyHGrid\"), \" work the same as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UICollectionView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UITableView\"), \" in such a way that they don't load all the data at the same time, they only load the one that are visible on the screen, this is  better for performance . The next update will be the SinglePass app. I have a bunch of projects in the work, subscribe if you haven't done so already to not miss out and share this article. Happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#initial-cleanup","title":"Initial cleanup"},{"url":"#now-playing","title":"Now Playing"},{"url":"#popular--top-rated","title":"Popular & Top rated"},{"url":"#upcoming","title":"Upcoming"},{"url":"#dynamic-container","title":"Dynamic Container"},{"url":"#section-view","title":"Section View"},{"url":"#update-the-moviestoreapp","title":"Update the MovieStoreApp"},{"url":"#conclusion","title":"Conclusion"}]}}}}]}},"pageContext":{"slug":"movie-booking-app"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}