Skip to content

Instantly share code, notes, and snippets.

@jdan
Created September 18, 2020 12:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdan/f2b13eca0cd49518e068a500e5ddd5b6 to your computer and use it in GitHub Desktop.
Save jdan/f2b13eca0cd49518e068a500e5ddd5b6 to your computer and use it in GitHub Desktop.
jdan app
//
// ContentView.swift
// Shared
//
// Created by Jordan Scales on 7/25/20.
//
import SwiftUI
import SwiftUIRefresh
struct Response: Codable {
struct Includes: Codable {
let Asset: [Asset]
}
let total: Int
let skip: Int
let limit: Int
let items: [Item]
let includes: Includes
}
struct MainApp: View {
let response: Response
let onRefresh: () -> Void
@State private var isRefreshing = false
var body: some View {
List {
ForEach(response.items, id: \.sys.id) { item in
PostView(
post: item.fields,
assets: response.includes.Asset
).listRowInsets(EdgeInsets())
}
}
.pullToRefresh(isShowing: $isRefreshing) {
onRefresh()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.isRefreshing = false
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MainApp(response: testResponse!, onRefresh: {})
}
}
struct ContentView: View {
@State private var response: Response? = nil
func loadData() {
guard let url = URL(string: "[REDACTED]") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let decodedResponse = try JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
self.response = decodedResponse
}
return
} catch {
print(error)
}
}
}.resume()
}
var body: some View {
if response != nil {
MainApp(response: response!, onRefresh: loadData)
} else {
Text("Loading...").onAppear(perform: loadData)
}
}
}
//
// Post.swift
// jdan
//
// Created by Jordan Scales on 7/25/20.
//
import SwiftUI
struct Item: Codable {
let sys: Metadata
let fields: Post
}
struct Metadata: Codable {
var id: String
var createdAt: Date
var updatedAt: Date
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
let strCreatedAt = try values.decode(String.self, forKey: .createdAt)
let strUpdatedAt = try values.decode(String.self, forKey: .updatedAt)
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// 2020-07-25T01:36:36.946Z
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSZ"
createdAt = dateFormatter.date(from: strCreatedAt)!
updatedAt = dateFormatter.date(from: strUpdatedAt)!
}
}
struct Asset: Codable {
struct AssetMetaData: Codable {
var id: String
var createdAt: Date
var updatedAt: Date
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
let strCreatedAt = try values.decode(String.self, forKey: .createdAt)
let strUpdatedAt = try values.decode(String.self, forKey: .updatedAt)
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// 2020-07-25T01:36:36.946Z
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSZ"
createdAt = dateFormatter.date(from: strCreatedAt)!
updatedAt = dateFormatter.date(from: strUpdatedAt)!
}
init(id: String) {
self.id = id
self.createdAt = Date()
self.updatedAt = Date()
}
}
struct Dimensions: Codable {
let width: Int
let height: Int
}
struct Details: Codable {
let size: Int
let image: Dimensions
}
struct File: Codable {
let url: String
let details: Details
let fileName: String
let contentType: String
}
struct Image: Codable {
let title: String
let description: String
let file: File
}
var sys: AssetMetaData
var fields: Image
init(id: String) {
self.sys = AssetMetaData(id: id)
self.fields = Image(
title: "Title",
description: "Description",
file: File(
url: "example.com",
details: Details(
size: 100,
image: Dimensions(width: 10, height: 10)
),
fileName: "file",
contentType: "image/png"
)
)
}
}
struct AssetReference: Codable {
struct Item: Codable {
let id: String
}
var sys: Item
init(id: String) {
self.sys = Item(id: id)
}
}
struct Post: Codable {
var date: Date
var title: String
var body: String
var gallery: [AssetReference]?
var score: Int
init(title: String, date: Date, body: String, gallery: [AssetReference]) {
self.date = date
self.title = title
self.body = body
self.gallery = gallery
self.score = 0
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decode(String.self, forKey: .title)
body = try values.decode(String.self, forKey: .body)
gallery = try? // gallery might be missing
values.decode(Array<AssetReference>.self, forKey: .gallery)
score = try values.decode(Int.self, forKey: .score)
let strDate = try values.decode(String.self, forKey: .date)
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// 2020-07-25T08:57-04:00
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mmZZZZZ"
date = dateFormatter.date(from: strDate)!
}
}
extension Date {
func timeAgo() -> String {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.year, .month, .day, .hour, .minute, .second]
formatter.zeroFormattingBehavior = .dropAll
formatter.maximumUnitCount = 1
return String(format: formatter.string(from: self, to: Date()) ?? "", locale: .current)
}
}
struct PostView: View {
let post: Post
let assets: [Asset]
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack(alignment: .top) {
Image("avatar")
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(20)
VStack(alignment: .leading) {
Text("jdan").fontWeight(.bold)
Text(post.title).font(.subheadline)
}
Spacer()
Text(post.date.timeAgo())
.font(.caption)
.foregroundColor(Color(UIColor.secondaryLabel))
}.padding(.horizontal)
Text(post.body)
.font(.title3)
.fontWeight(.light)
.padding(.horizontal)
if post.gallery != nil && post.gallery!.count > 0 {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(post.gallery!, id: \.sys.id) { galleryItem in
URLImage(
withURL: assets
.first(where: { $0.sys.id == galleryItem.sys.id })!
.fields.file.url
)
}
}.padding(.horizontal)
}
}
}.padding(.vertical)
}
}
struct PostView_Previews: PreviewProvider {
static var previews: some View {
Group {
PostView(
post: Post(title: "just vibin", date: Date(), body: "Hello, world!", gallery: []),
assets: []
).padding().previewLayout(.sizeThatFits)
PostView(
post: Post(title: "just vibin", date: Date(), body: "Hello, world!", gallery: [
AssetReference(id: "1")
]),
assets: [
Asset(id: "1"),
Asset(id: "2")
]
)
.previewLayout(.fixed(width: 414, height: 500))
.previewDisplayName("One image")
PostView(
post: Post(
title: "just vibin",
date: Date(),
body: "Hello, world!",
gallery: [
AssetReference(id: "1"),
AssetReference(id: "2"),
AssetReference(id: "3")
]
),
assets: [
Asset(id: "1"),
Asset(id: "2"),
Asset(id: "3")
]
)
.previewLayout(.fixed(width: 414, height: 500))
.previewDisplayName("Multiple images")
}
}
}
//
// URLImage.swift
// jdan
//
// Created by Jordan Scales on 7/25/20.
//
import SwiftUI
class ImageLoader: ObservableObject {
@Published var data: Data?
init(urlString: String) {
// Why do I need to put https in myself
guard let url = URL(string: "https:\(urlString)") else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
self.data = data
}
}
task.resume()
}
}
struct URLImage: View {
#if !targetEnvironment(simulator)
@ObservedObject var imageLoader: ImageLoader
#endif
@State var image: UIImage = UIImage()
init(withURL url: String) {
#if !targetEnvironment(simulator)
imageLoader = ImageLoader(urlString: url)
#endif
}
let screenWidth = UIScreen.main.bounds.size.width - 32
var body: some View {
VStack {
#if targetEnvironment(simulator)
Image("me")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: screenWidth, height: screenWidth)
#else
Image(uiImage: imageLoader.data != nil ? UIImage(data:imageLoader.data!)! : UIImage())
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: screenWidth, height: screenWidth)
#endif
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment