sunshinejr / sunshinejr.github.io

Statically generated frontend for my website. Using Hugo.
https://sunshinejr.com
4 stars 1 forks source link

Using Xcode Preview with your production code #8

Open sunshinejr opened 5 years ago

fifty8 commented 5 years ago

Hi thanks for the intro! Question tho— how did you make the preview canvas show up? I tried switching between Editor Only and Editor and Canvas but it has no effect. Guess it’s an early beta bug? Thanks!

sunshinejr commented 5 years ago

Hey @fifty8. It's tricky when you use separate files for the preview. I have two editors, both having the Editor + Canvas setting. Then, I open one file that is the proper controller file and one with the extension. On the side with the extension I can see a canvas + editor split in half and then I can make the canvas up to the top so you don't even see the editor anymore!

Xcode two-editor preview setup

If you don't see it the same way, then yeah it's probably a bug.

LeonidKokhnovich commented 5 years ago

It does require OS X Beta installed just in case for those who are trying on the latest released OS X. Otherwise, the Canvas view just won't show up.

Que20 commented 5 years ago

So I wanted to play with this and I got this weird error, could you help me ?

Protocol 'View' requirement '_makeView(view:inputs:)' cannot be satisfied by a non-final class ('ViewController') because it uses 'Self' in a non-parameter, non-result type position

I got this error on every line of the extension...

This is the code :

import UIKit
import SwiftUI

public class ViewController: UIViewController {
    override public func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.red
    }
}

extension ViewController: UIViewControllerRepresentable {

    public typealias UIViewControllerType = ViewController

    public func makeUIViewController(context: UIViewControllerRepresentableContext<UIViewControllerType>) -> ViewController.UIViewControllerType {
        return ViewController()
    }

    public func updateUIViewController(_ uiViewController: ViewController, context: UIViewControllerRepresentableContext<UIViewControllerType>) {
        //
    }
}

What am I doing wrong ?

I could just use some kind of wrapper struct that'd look like :

struct ViewControllerWrapper: UIViewControllerRepresentable {
    typealias UIViewControllerType = ViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<ViewControllerWrapper>) -> ViewControllerWrapper.UIViewControllerType {
        return ViewController()
    }

    func updateUIViewController(_ uiViewController: ViewControllerWrapper.UIViewControllerType, context: UIViewControllerRepresentableContext<ViewControllerWrapper>) {
        //
    }
}

But I wanted to try your approach of making the actual ViewController UIViewControllerRepresentable. But I can't make it work like you do.

LeonidKokhnovich commented 5 years ago

@Que20 As a temporary solution, you can make the VC class final

Que20 commented 5 years ago

Thanks for your answer. What if I want to initialize my viewController with parameters, say a string or a URL ? I cannot just extend my (now final) UIViewController class, I need to go for that kind of wrapper, right ?

LeonidKokhnovich commented 5 years ago

@Que20 You should be fine if you just want to add a custom init to a final class like in the code example below:

final class TestVC: UIViewController {
    let number: Int

    init(number: Int) {
        self.number = number
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        self.number = 0
        super.init(coder: aDecoder)
    }
}
szotp commented 4 years ago

I managed to reduce the amount of boilerplate to this:

#if canImport(SwiftUI) && DEBUG
import SwiftUI
class ViewController_Previews: Previewer<ViewController>, PreviewProvider {}
#endif

And it looks like in latest Xcode you don't even need to change the deployment target. So IMO there is no need to setup extra schemes, targets, etc., just add those few lines and maybe a setup function to inject fake view model.

I solved the hot reload problem by having an UINavigationController as the main representable, and then swapping the view controller inside it on every hot reload.

More info in my repo: https://github.com/szotp/programmatic_preview