swift中让UIViewController和UIView具有Live Preview效果

5,908 阅读2分钟

Live Preview功能能让我们在开发过程中实时看到UI层面的改动效果,不用每次稍有改动就得运行整个APP,并且跳转到对应的页面才能看到实际效果,这必然会极大的提升开发效率。SwiftUIPreview是为SwiftUI设计的,但是在UIKit环境下也可以使用,本文针对这个场景的实践做一个记录。

新建一个swift项目,删除Storyboard

在info.plist中删除

<key>UISceneStoryboardFile</key>  
<string>Main</string>

在SceneDelegate中,设置window的根控制器

guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let vc = ViewController()
let nav = UINavigationController(rootViewController: vc)
window.rootViewController = nav
window.makeKeyAndVisible()
self.window = window

给UIView和UIViewController 添加 Preview的extension

创建UIViewControllerExtension.swift文件

import UIKit
    import SwiftUI

    extension UIViewController {
        @available(iOS 13, *)
        private struct Preview: UIViewControllerRepresentable {
            // 用于注入当前的viewcontroller
            let viewController: UIViewController
            
            func makeUIViewController(context: Context) -> UIViewController {
                return viewController
            }
            
            func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
                //
            }
        }
        
        @available(iOS 13, *)
        func showPreview() -> some View {
            Preview(viewController: self)
        }
    }

创建UIViewExtension.swift

import UIKit
import SwiftUI

extension UIView {preview-uikit-views-in-xcode-3543
    @available(iOS 13, *)
    private struct Preview: UIViewRepresentable {
        typealias UIViewType = UIView
        let view: UIView
        func makeUIView(context: Context) -> UIView {
            return view
        }
        
        func updateUIView(_ uiView: UIView, context: Context) {}
    }
    
    @available(iOS 13, *)
    func showPreview() -> some View {
        // inject self (the current UIView) for the preview
        Preview(view: self)
    }
}

具体使用?

新建一个用户信息的view,添加imageview、和两个label,约束用的是SnapKit

import UIKit
import SnapKit

class UserInfo: UIView{
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUpView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setUpView()
    }
    
    lazy var titleLabel: UILabel = {
        let label = UILabel()
        label.text = "我的信息"
        label.font = .boldSystemFont(ofSize: 24)
        label.textAlignment = .left
        return label
    }()
    
    let imageWidth = 60.0
    lazy var profileImageView: UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "yaofan"))
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = imageWidth / 2
        imageView.layer.borderWidth = 1
        imageView.layer.borderColor = UIColor.black.withAlphaComponent(0.1).cgColor
        return imageView
    }()
    
    lazy var nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18)
        label.text = "Luseike"
        return label
    }()
    
    lazy var roleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 15)
        label.textColor = .gray
        label.text = "iOS Engineer"
        return label
    }()
    
    lazy var nameStackView: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [nameLabel, roleLabel])
        stack.axis = .vertical
        stack.alignment = .leading
        stack.spacing = 4
        return stack
    }()
    
    
    func setUpView(){
        self.addSubview(titleLabel)
        self.addSubview(profileImageView)
        self.addSubview(nameStackView)
        
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(self.safeAreaLayoutGuide.snp.top).offset(10)
            make.leading.trailing.equalToSuperview().inset(24)
        }
        
        profileImageView.snp.makeConstraints { make in
            make.width.height.equalTo(imageWidth)
            make.top.equalTo(titleLabel.snp.bottom).offset(16)
            make.leading.equalToSuperview().offset(20)
        }
        
        nameStackView.snp.makeConstraints { make in
            make.leading.equalTo(profileImageView.snp.trailing).offset(12)
            make.centerY.equalTo(profileImageView.snp.centerY)
            make.trailing.equalToSuperview().offset(-20)
        }   
    }
}

在view底部,添加一个继承PreviewProvider的struct,因为上面已经添加了UIView的扩展,会call到showPreview()使其有能力来preview我们自定义的view

#if DEBUG
import SwiftUI

@available(iOS 13, *)
struct HeaderView_Preview: PreviewProvider {
    static var previews: some View {
        HeaderView().showPreview()
    }
}
#endif

截屏2023-05-26 11.19.23.png

controller文件的preview使用跟view是类似的

#if DEBUG
import SwiftUI

@available(iOS 13, *)
struct ViewController_Preview: PreviewProvider {
    static var previews: some View {
        ViewController().showPreview()
    }
}
#endif