ZaraReplica -Part 2

Intro to redux and Home screen

State and change to state (Action)

Reducer

import Foundationclass Store: ObservableObject {			struct State {
var products: [Product]
var bookmarked: [Product]
var shoppingCart: [Product]
var isLoggedIn: Bool = false
}
enum Action {
case addProducts(_ products: [Product])
case bookmark(_ product: Product)
case removeFromBookmark(_ index: Int)
case addToCart(_ product: Product)
case login
}


@Published private(set) var state: State = .init(products: [], bookmarked: [], shoppingCart: [])


func dispatch(_ action: Action) {
reducer(state: &state, action: action)
}

func reducer(state: inout State, action: Action) {
switch action {
case .bookmark(let product):
state.bookmarked.append(product)
case .addToCart(let product):
state.shoppingCart.append(product)
case .removeFromBookmark(let index):
state.bookmarked.remove(at: index)
case .addProducts(let products):
state.products = products
case .login:
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { [weak self] in
self?.state.isLoggedIn = true
}
}
}
}

UIKit

import SwiftUIstruct CollectionView: UIViewRepresentable {

var controllers: [UIViewController]
@Binding var currentPage: Int
@Binding var selectedIndex: Int?

func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .clear
collectionView.register(CollectionCell.self, forCellWithReuseIdentifier: CollectionCell.reuseId)
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.isPagingEnabled = true
collectionView.alwaysBounceVertical = false
collectionView.bounces = false
collectionView.showsVerticalScrollIndicator = false
layout.minimumLineSpacing = 0
return collectionView
}

func updateUIView(_ collectionView: UICollectionView, context: Context) {

}

func makeCoordinator() -> CollectionView.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject,UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var parent: CollectionView

init(_ parent: CollectionView) {
self.parent = parent
}

func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parent.controllers.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionCell.reuseId, for: indexPath) as? CollectionCell {
cell.viewController = parent.controllers[indexPath.item]
return cell
}
return UICollectionViewCell()
}

func collectionView(_ collectionView: UICollectionView,
didEndDisplaying cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
guard let index = collectionView.indexPathsForVisibleItems.first?.item else {
return
}

self.parent.currentPage = index
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.parent.selectedIndex = indexPath.item
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
}
}
import SwiftUIclass CollectionCell: UICollectionViewCell {
static let reuseId: String = "CollectionCell"

// 1
var viewController: UIViewController? {
didSet {
if let viewController = self.viewController {
setupCell(viewController)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
if let viewController = self.viewController {
setupCell(viewController)
}
}

required init?(coder: NSCoder) {
super.init(coder: coder)
}


// 2
private func setupCell(_ viewController: UIViewController){

guard let view = viewController.view else {
return
}

view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
self.addSubview(view)

NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
view.topAnchor.constraint(equalTo: contentView.topAnchor),
view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
}

}

Page View

import SwiftUIstruct PageView: View {

var page: Page

var body: some View {

VStack(spacing: 20) {
Text(page.title)
.multilineTextAlignment(.center)
.font(.system(size: 50, weight: Font.Weight.black, design: Font.Design.default))
.textColor()
.frame(width: Sizes.screenWidth * 0.95, alignment: .center)

Text(page.description)
.textColor()
.multilineTextAlignment(.center)
.frame(width: 300, alignment: .center)

Text("View")
.font(.system(size: 20, weight: Font.Weight.bold, design: Font.Design.default))
.textColor()
.padding(.horizontal, 30)
.padding(.vertical, 10)
.border(Color.text, width: 1)
}.frame(height: Sizes.screenHeight)
.bgColor()
.edgesIgnoringSafeArea(.all)

}
}
struct PageView_Previews: PreviewProvider {
static var previews: some View {
PageView(page: Page.default)
}
}

Vertical Page Indicator

import SwiftUIstruct VerticalPageIndicator: View {
var numberOfPages: Int = 5
@Binding var selectedPageIndex: Int

var body: some View {
VStack {
ForEach(0..<numberOfPages) { i in
if i == selectedPageIndex {
Circle()
.strokeBorder(lineWidth: 1.0, antialiased: true)
.frame(width: 12, height: 12, alignment: .center)
} else {
Circle().frame(width: 7, height: 7, alignment: .center)
}

}
}.textColor()
}
}
struct VerticalPageIndicator_Previews: PreviewProvider {
static var previews: some View {
VerticalPageIndicator(selectedPageIndex: .constant(0))
.preferredColorScheme(.dark)
}
}
struct PagingController<T: View>: View {

var viewControllers: [UIHostingController<T>] = []
@State var currentPage = 0
@Binding var selectedIndex: Int?

var body: some View {

return ZStack(alignment: .trailing) {
CollectionView(controllers: viewControllers, currentPage: self.$currentPage, selectedIndex: self.$selectedIndex)
VerticalPageIndicator(numberOfPages: viewControllers.count, selectedPageIndex: self.$currentPage)
.padding(.trailing, 10)
}
}
}

struct PagingController_Previews: PreviewProvider {
static var previews: some View {
PagingController(viewControllers: Page.data.map({ UIHostingController(rootView: PageView(page: $0) )}))
}
}
Swift ui paging view page indicator

Tab Indicator

struct TabIndicatorItem: View {

var category: Category
var isActive: Bool


var body: some View {

return VStack(spacing: 0) {
Text(category.toString().uppercased())
.font(.system(size: 18,
weight: isActive ? Font.Weight.bold : Font.Weight.light,
design: Font.Design.default))
.frame(maxWidth: .infinity)
.layoutPriority(1)

if isActive {
Rectangle()
.frame(width: 50 , height: 2, alignment: .center)
}
}.foregroundColor(.text)
}
}
struct TabIndicator: View {
var category: Category

var body: some View {
HStack{
ForEach(0..<Category.allCases.count) { i in
TabIndicatorItem(category: Category.allCases[i], isActive: Category.allCases[i] == category ) }
}.frame(width: 250)
}
}
struct TabIndicator_Previews: PreviewProvider {
static var previews: some View {
TabIndicator(category: Category.men)
}
}
import SwiftUIstruct HomeScreen: View {

@Binding var category: Category?

private let data = Home.data
@State private var selected = Home.data.first!
@EnvironmentObject private var store: Store

var body: some View {

return ZStack(alignment: .top) {
TabView(selection: self.$selected.id) {
ForEach(data) { item in
createPageController(item)
.frame(height: Sizes.screenHeight)
.tag(item.id)
.onTapGesture(count: 1, perform: { category = item.id })
}

}.background(Color.background)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
TabIndicator(category: selected.id)
.padding(.top, 50)
}
}

fileprivate func createPageController(_ data: Home) -> PagingController<PageView> {
return PagingController(
viewControllers: data.pages.map({ UIHostingController(rootView: PageView(page: $0) )})
)
}
}
struct HomeScreen_Previews: PreviewProvider {
static var previews: some View {
HomeScreen(category: .constant(Category.kids))
}
}

Building real world apps.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store