Skip to content

Commit 11e9731

Browse files
author
Mat Schmid
committed
Demo app
1 parent 7d446c3 commit 11e9731

17 files changed

+1008
-2
lines changed

.gitignore

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## User settings
6+
xcuserdata/
7+
8+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9+
*.xcscmblueprint
10+
*.xccheckout
11+
12+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13+
build/
14+
DerivedData/
15+
*.moved-aside
16+
*.pbxuser
17+
!default.pbxuser
18+
*.mode1v3
19+
!default.mode1v3
20+
*.mode2v3
21+
!default.mode2v3
22+
*.perspectivev3
23+
!default.perspectivev3
24+
25+
## Xcode Patch
26+
*.xcodeproj/*
27+
!*.xcodeproj/project.pbxproj
28+
!*.xcodeproj/xcshareddata/
29+
!*.xcworkspace/contents.xcworkspacedata
30+
/*.gcno

Models.swift

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Models.swift
3+
// Products
4+
//
5+
// Created by Mat Schmid on 2019-06-10.
6+
// Copyright © 2019 Shopify. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
typealias UINetworkModel = Decodable & Identifiable
12+
13+
struct Root: Decodable {
14+
var products: [Product]
15+
}
16+
17+
struct Product: UINetworkModel {
18+
var id: Int
19+
20+
let title, body_html, vendor: String
21+
let variants: [Variant]
22+
let image: ProductImage
23+
}
24+
25+
struct Variant: UINetworkModel {
26+
var id: Int
27+
28+
let title, price: String
29+
}
30+
31+
struct ProductImage: UINetworkModel {
32+
var id: Int
33+
34+
let src: String
35+
}

ProductDetailsView.swift

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// ProductDetailsView.swift
3+
// Products
4+
//
5+
// Created by Mat Schmid on 2019-06-10.
6+
// Copyright © 2019 Shopify. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
struct ProductDetailsView : View {
12+
var product: Product
13+
14+
var body: some View {
15+
NavigationView {
16+
VStack(alignment: .leading) {
17+
ProductRow(product: product)
18+
Text("Variants").font(.title).padding()
19+
List(product.variants) { variant in
20+
VStack(alignment: .leading) {
21+
Text(variant.title)
22+
Text(variant.price)
23+
}
24+
}
25+
}
26+
.navigationBarTitle(Text("Details"))
27+
}
28+
29+
}
30+
}

ProductFetcher.swift

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// ProductFetcher.swift
3+
// Products
4+
//
5+
// Created by Mat Schmid on 2019-06-10.
6+
// Copyright © 2019 Shopify. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import SwiftUI
11+
import Combine
12+
13+
enum LoadableState<T> {
14+
case loading
15+
case fetched(Result<T, FetchError>)
16+
}
17+
18+
enum FetchError: Error {
19+
case error(String)
20+
21+
var localizedDescription: String {
22+
switch self {
23+
case .error(let message):
24+
return message
25+
}
26+
}
27+
}
28+
29+
class ProductFetcher: BindableObject {
30+
private static let apiUrlString = "https://gist.githubusercontent.com/schmidyy/02fdec9b9e05a71312a550fc50f948e6/raw/7fc2facbbf9c3aa526f35a32d0c7fe74a4fc29a1/products.json"
31+
var didChange = PassthroughSubject<ProductFetcher, Never>()
32+
33+
var state: LoadableState<Root> = .loading {
34+
didSet {
35+
didChange.send(self)
36+
}
37+
}
38+
39+
init() {
40+
guard let apiUrl = URL(string: ProductFetcher.apiUrlString) else {
41+
state = .fetched(.failure(.error("Malformed API URL.")))
42+
return
43+
}
44+
45+
URLSession.shared.dataTask(with: apiUrl) { [weak self] (data, _, error) in
46+
if let error = error {
47+
self?.state = .fetched(.failure(.error(error.localizedDescription)))
48+
return
49+
}
50+
51+
guard let data = data else {
52+
self?.state = .fetched(.failure(.error("Malformed response data")))
53+
return
54+
}
55+
let root = try! JSONDecoder().decode(Root.self, from: data)
56+
57+
DispatchQueue.main.async { [weak self] in
58+
self?.state = .fetched(.success(root))
59+
}
60+
}.resume()
61+
}
62+
}
63+
64+
class ImageFetcher: BindableObject {
65+
var didChange = PassthroughSubject<Data, Never>()
66+
67+
var data: Data = Data() {
68+
didSet {
69+
didChange.send(data)
70+
}
71+
}
72+
73+
init(url: String) {
74+
guard let imageUrl = URL(string: url) else {
75+
return
76+
}
77+
78+
URLSession.shared.dataTask(with: imageUrl) { (data, _, _) in
79+
guard let data = data else { return }
80+
DispatchQueue.main.async { [weak self] in
81+
self?.data = data
82+
}
83+
}.resume()
84+
}
85+
}

ProductListView.swift

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// ProductListView.swift
3+
// Products
4+
//
5+
// Created by Mat Schmid on 2019-06-10.
6+
// Copyright © 2019 Shopify. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
struct ProductListView : View {
12+
@ObjectBinding var productFetcher = ProductFetcher()
13+
14+
private var stateContent: AnyView {
15+
switch productFetcher.state {
16+
case .loading:
17+
return AnyView(
18+
ActivityIndicator(style: .medium)
19+
)
20+
case .fetched(let result):
21+
switch result {
22+
case .failure(let error):
23+
return AnyView(
24+
Text(error.localizedDescription)
25+
)
26+
case .success(let root):
27+
return AnyView(
28+
List(root.products) { product in
29+
NavigationButton(destination: ProductDetailsView(product: product)) {
30+
ProductRow(product: product)
31+
}
32+
}
33+
)
34+
}
35+
}
36+
}
37+
38+
var body: some View {
39+
NavigationView {
40+
stateContent
41+
.navigationBarTitle(Text("Products"))
42+
}
43+
}
44+
}

ProductRow.swift

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// ProductRow.swift
3+
// Products
4+
//
5+
// Created by Mat Schmid on 2019-06-10.
6+
// Copyright © 2019 Shopify. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
struct ProductRow: View {
12+
var product: Product
13+
14+
var body: some View {
15+
HStack {
16+
LoadableImageView(with: product.image.src).frame(width: 60, height: 60)
17+
VStack(alignment: .leading) {
18+
Text(product.title)
19+
Text(product.body_html).font(.caption)
20+
}
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)