Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@

import UIKit

let url = NSURL(string: "http://localhost:8000/episode.json")!
let url = URL(string: "http://localhost:8000/episode.json")!
let episodeResource = Resource<Episode>(url: url, parseJSON: { anyObject in
(anyObject as? JSONDictionary).flatMap(Episode.init)
})


final class LoadingViewController: UIViewController {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
Copy link
Copy Markdown
Author

@azarovalex azarovalex Nov 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.Gray style is now deprecated in favor of .medium

let spinner = UIActivityIndicatorView(style: .medium)

init<A>(load: ((Result<A>) -> ()) -> (), build: (A) -> UIViewController) {
init<A>(load: (@escaping (Result<A>) -> ()) -> (), build: @escaping (A) -> UIViewController) {
super.init(nibName: nil, bundle: nil)
spinner.startAnimating()
load() { [weak self] result in
Expand All @@ -28,7 +28,7 @@ final class LoadingViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
view.backgroundColor = .white
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
Expand All @@ -39,12 +39,12 @@ final class LoadingViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}

func add(content content: UIViewController) {
addChildViewController(content)
func add(content: UIViewController) {
addChild(content)
view.addSubview(content.view)
content.view.translatesAutoresizingMaskIntoConstraints = false
content.view.constrainEdges(toMarginOf: view)
content.didMoveToParentViewController(self)
content.didMove(toParent: self)
}
}

Expand All @@ -58,7 +58,7 @@ final class EpisodeDetailViewController: UIViewController {
}

override func viewDidLoad() {
view.backgroundColor = .whiteColor()
view.backgroundColor = .white

view.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -70,11 +70,11 @@ final class EpisodeDetailViewController: UIViewController {
let sharedWebservice = Webservice()

let episodesVC = LoadingViewController(load: { callback in
sharedWebservice.load(episodeResource, completion: callback)
sharedWebservice.load(resource: episodeResource, completion: callback)
}, build: EpisodeDetailViewController.init)

episodesVC.view.frame = CGRect(x: 0, y: 0, width: 250, height: 300)


import XCPlayground
XCPlaygroundPage.currentPage.liveView = episodesVC
import PlaygroundSupport
PlaygroundPage.current.liveView = episodesVC
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import UIKit

let url = NSURL(string: "http://localhost:8000/episode.json")!
let url = URL(string: "http://localhost:8000/episode.json")!
let episodeResource = Resource<Episode>(url: url, parseJSON: { anyObject in
(anyObject as? JSONDictionary).flatMap(Episode.init)
})
Expand All @@ -24,27 +24,27 @@ protocol Loading {
extension Loading where Self: UIViewController {
func load(resource: Resource<ResourceType>) {
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
sharedWebservice.load(resource: resource) { [weak self] result in
self?.spinner.stopAnimating()
guard let value = result.value else { return } // TODO loading error
self?.configure(value)
self?.configure(value: value)
}
}
}


final class EpisodeDetailViewController: UIViewController, Loading {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
let spinner = UIActivityIndicatorView(style: .medium)
let titleLabel = UILabel()

convenience init(episode: Episode) {
self.init()
configure(episode)
configure(value: episode)
}

convenience init(resource: Resource<Episode>) {
self.init()
load(resource)
load(resource: resource)
}

func configure(value: Episode) {
Expand All @@ -53,7 +53,7 @@ final class EpisodeDetailViewController: UIViewController, Loading {

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
view.backgroundColor = .white

spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -71,5 +71,5 @@ let episodesVC = EpisodeDetailViewController(resource: episodeResource)
episodesVC.view.frame = CGRect(x: 0, y: 0, width: 250, height: 300)


import XCPlayground
XCPlaygroundPage.currentPage.liveView = episodesVC
import PlaygroundSupport
PlaygroundPage.current.liveView = episodesVC
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import UIKit

let url = NSURL(string: "http://localhost:8000/episode.json")!
let url = URL(string: "http://localhost:8000/episode.json")!
let episodeResource = Resource<Episode>(url: url, parseJSON: { anyObject in
(anyObject as? JSONDictionary).flatMap(Episode.init)
})
Expand All @@ -16,7 +16,7 @@ let sharedWebservice = Webservice()


final class EpisodeDetailViewController: UIViewController {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
let spinner = UIActivityIndicatorView(style: .medium)
let titleLabel = UILabel()

convenience init(episode: Episode) {
Expand All @@ -27,7 +27,7 @@ final class EpisodeDetailViewController: UIViewController {
convenience init(resource: Resource<Episode>) {
self.init()
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
sharedWebservice.load(resource: resource) { [weak self] result in
self?.spinner.stopAnimating()
guard let value = result.value else { return } // TODO loading error
self?.titleLabel.text = value.title
Expand All @@ -36,7 +36,7 @@ final class EpisodeDetailViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
view.backgroundColor = .white

spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -54,5 +54,5 @@ let episodesVC = EpisodeDetailViewController(resource: episodeResource)
episodesVC.view.frame = CGRect(x: 0, y: 0, width: 250, height: 300)


import XCPlayground
XCPlaygroundPage.currentPage.liveView = episodesVC
import PlaygroundSupport
PlaygroundPage.current.liveView = episodesVC
55 changes: 37 additions & 18 deletions Loading View Controllers.playground/Sources/Helpers.swift
Original file line number Diff line number Diff line change
@@ -1,38 +1,57 @@
import UIKit

extension UIView {
public func constrainEqual(attribute: NSLayoutAttribute, to: AnyObject, multiplier: CGFloat = 1, constant: CGFloat = 0) {
constrainEqual(attribute, to: to, attribute, multiplier: multiplier, constant: constant)
public func constrainEqual(
attribute: NSLayoutConstraint.Attribute,
to: AnyObject,
multiplier: CGFloat = 1,
constant: CGFloat = 0
) {
constrainEqual(attribute: attribute, to: to, attribute, multiplier: multiplier, constant: constant)
}

public func constrainEqual(attribute: NSLayoutAttribute, to: AnyObject, _ toAttribute: NSLayoutAttribute, multiplier: CGFloat = 1, constant: CGFloat = 0) {
NSLayoutConstraint.activateConstraints([
NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .Equal, toItem: to, attribute: toAttribute, multiplier: multiplier, constant: constant)
]
)
public func constrainEqual(
attribute: NSLayoutConstraint.Attribute,
to: AnyObject,
_ toAttribute: NSLayoutConstraint.Attribute,
multiplier: CGFloat = 1,
constant: CGFloat = 0
) {
NSLayoutConstraint.activate([
NSLayoutConstraint(
item: self,
attribute: attribute,
relatedBy: .equal,
toItem: to,
attribute: toAttribute,
multiplier: multiplier,
constant: constant
)
])
}

public func constrainEdges(toMarginOf view: UIView) {
constrainEqual(.Top, to: view, .TopMargin)
constrainEqual(.Leading, to: view, .LeadingMargin)
constrainEqual(.Trailing, to: view, .TrailingMargin)
constrainEqual(.Bottom, to: view, .BottomMargin)
constrainEqual(attribute: .top, to: view, .topMargin)
constrainEqual(attribute: .leading, to: view, .leadingMargin)
constrainEqual(attribute: .trailing, to: view, .trailingMargin)
constrainEqual(attribute: .bottom, to: view, .bottomMargin)
}

public func center(inView view: UIView) {
centerXAnchor.constrainEqual(view.centerXAnchor)
centerYAnchor.constrainEqual(view.centerYAnchor)
centerXAnchor.constrainEqual(anchor: view.centerXAnchor)
centerYAnchor.constrainEqual(anchor: view.centerYAnchor)
}
}

extension NSLayoutAnchor {
public func constrainEqual(anchor: NSLayoutAnchor, constant: CGFloat = 0) {
let constraint = constraintEqualToAnchor(anchor, constant: constant)
constraint.active = true
@objc public func constrainEqual(anchor: NSLayoutAnchor, constant: CGFloat = 0) {
constraint(equalTo: anchor, constant: constant).isActive = true
}
}


public func mainQueue(block: () -> ()) {
dispatch_async(dispatch_get_main_queue(), block)
public func mainQueue(block: @escaping () -> ()) {
DispatchQueue.main.async {
block()
}
}
20 changes: 10 additions & 10 deletions Loading View Controllers.playground/Sources/Networking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public typealias JSONDictionary = [String: AnyObject]
extension Episode {
public init?(json: JSONDictionary) {
guard let id = json["id"] as? String,
title = json["title"] as? String else { return nil }
let title = json["title"] as? String else { return nil }

self.id = id
self.title = title
Expand All @@ -26,15 +26,15 @@ extension Episode {


public struct Resource<A> {
public var url: NSURL
public var parse: NSData -> A?
public var url: URL
public var parse: (Data) -> A?
}

extension Resource {
public init(url: NSURL, parseJSON: AnyObject -> A?) {
public init(url: URL, parseJSON: @escaping (Any) -> A?) {
self.url = url
self.parse = { data in
let json = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
return json.flatMap(parseJSON)
}
}
Expand All @@ -43,11 +43,11 @@ extension Resource {

public enum Result<A> {
case success(A)
case error(ErrorType)
case error(Error)
}

extension Result {
public init(_ value: A?, or error: ErrorType) {
public init(_ value: A?, or error: Error) {
if let value = value {
self = .success(value)
} else {
Expand All @@ -62,7 +62,7 @@ extension Result {
}


public enum WebserviceError: ErrorType {
public enum WebserviceError: Error {
case other
}

Expand All @@ -71,8 +71,8 @@ public final class Webservice {
public init() { }

/// Loads a resource. The completion handler is always called on the main queue.
public func load<A>(resource: Resource<A>, completion: Result<A> -> ()) {
NSURLSession.sharedSession().dataTaskWithURL(resource.url) { data, response, _ in
public func load<A>(resource: Resource<A>, completion: @escaping (Result<A>) -> ()) {
URLSession.shared.dataTask(with: resource.url) { data, response, _ in
let parsed = data.flatMap(resource.parse)
let result = Result(parsed, or: WebserviceError.other)
mainQueue { completion(result) }
Expand Down