Fat GrinCatchlines, Articles, About

Un-react example playground

19 February 2017

I was reading through the facebook react native tutorial and wondered how much more code it would take to create the example with UIKit. Obviously this code would only work on iOS, not other platforms supported by React Native. Its also a bit longer than the example Javascript code, but doesn't rely on the React Native framework, so in that respect it's much shorter.

You can download the example Xcode playground here, or view as a gist here. Please excuse the forced unwrapping, the code is just a quick-and-dirty example.

import UIKit
import PlaygroundSupport

let cellIdentifier = "cellId"
let dataUrl = URL(string: "https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json")!

func contstraint(_ attr: NSLayoutAttribute, from: UIView, to: UIView) -> NSLayoutConstraint {
    return NSLayoutConstraint(
        item: from,
        attribute: attr,
        relatedBy: .equal,
        toItem: to,
        attribute: attr,
        multiplier: 1,
        constant: 1
    )
}

struct Movie {
    let title: String
    let thumbnail: URL
    init?(json: [String : Any]) {
        guard let title = json["title"] as? String,
              let images = json["posters"] as? [String : String],
              let tumbnailURLString = images["thumbnail"],
              let thumbnailURL = URL(string: tumbnailURLString)
        else {
            return nil
        }
        self.title = title
        self.thumbnail = thumbnailURL
    }
}

func loadMovies(completionHandler: @escaping ([Movie]) -> Void) {
    let task = URLSession(configuration: .default).dataTask(with: dataUrl) { (data, response, error) in
        if let data = data {
            do {
                if let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : Any],
                   let movies = json["movies"] as? [[String : Any]]
                {
                    completionHandler(movies.flatMap { Movie(json: $0) })
                }
            } catch {}
        }
    }
    task.resume()
}

extension UIImageView {
    func loadImage(url: URL, placeholder: String) {
        image = UIImage(named: placeholder)
        _ = URLSession(configuration: .default).dataTask(with: url) { [weak self] (data, response, error) in
            if let data = data, let img = UIImage(data: data) {
                DispatchQueue.main.async {
                    self?.image = img
                }
            } else if let err = error {
                print("Error: \(url) \(err)")
            } else {
                print("Error: \(url)")
            }
        }.resume()
    }
}

class MovieCell: UITableViewCell {
    init() {
        super.init(style: .default, reuseIdentifier: cellIdentifier)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

class MoviesListViewController: UITableViewController {
    var data: [Movie]

    init(movies: [Movie]) {
        data = movies
        super.init(style: .plain)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = makeCell(tableView)
        cell.textLabel?.text = data[indexPath.row].title
        cell.imageView?.loadImage(url: data[indexPath.row].thumbnail, placeholder: "placeholder")
        return cell
    }

    func makeCell(_ tableView: UITableView) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) else {
            return MovieCell()
        }
        return cell
    }
}

class LoadingView: UIView {
    var loadingLabel: UILabel = {
        let label = UILabel()
        label.text = "Loading..."
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.white
        addSubview(loadingLabel)
        addConstraints([
            contstraint(.centerX, from: loadingLabel, to: self),
            contstraint(.centerY, from: loadingLabel, to: self),
        ])
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

class RootViewController: UIViewController {
    var movieListVC: MoviesListViewController! = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        view = LoadingView(frame: view.frame)
        view.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
    }

    func loadMoviesAndPresent() {
        loadMovies() { [weak self] movies in
            guard self != nil else {
                return
            }
            print("Loaded \(movies.count) movies")
            self!.movieListVC = MoviesListViewController(movies: movies)
            self!.movieListVC.modalTransitionStyle = .crossDissolve
            self!.show(self!.movieListVC, sender: self)
        }
    }
}

let rootVC = RootViewController(nibName: nil, bundle: nil)
rootVC.loadMoviesAndPresent()
PlaygroundPage.current.liveView = rootVC

— Ryan Gibson —