试试在UIViewController内写SwiftUI

72 阅读1分钟
截屏2025-07-21 10.18.35.png

现在都2025年了,写UIKit写的烦。

那堆addSubView、makeConstraint写得整个人都红温了,就老想着用SwiftUI来写,可是又离不开当前的UIViewController的框架。

于是乎试了一下在UIViewController里面写SwiftUI,第一个想到的就是用UIHostingController:

class  MyViewController:
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let hostingController = UIHostingController(rootView: MySwiftUIView("blablabla")
        addChild(hostingController)
        hostingController.didMove(toParent: self)
        view.addSubview(hostingController.view)
        hostingController.view.frame = view.bounds
        hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
}

这样写很不灵活,每次都要创建UIHostingController、addSubview、addChild、setFrame之类的,照样很烦躁。

发现UIHostingController有个rootView属性,可以随地修改里面的SwiftUIView。嗯,将这个MyViewController改一下封装成一个父类,然后子类重写hostingController.rootView可以不?

class  MyViewController: UIViewController {
    var hostingController: UIHostingController!
    
    // viewdidLoad同上省略
}

截屏2025-07-18 16.39.09.png

问题出现了!UIHostingController是带泛型Content的,Content遵循View协议!必须给定一个固定的类型。可是这样子就又有问题,我根本不知道我Content会传什么类型的进去,子类重写的话可能还要变成另一种类型。

想到了!借用AnyView擦除类型

截屏2025-07-18 16.45.19.png

改一下

class MyViewController: UIViewController {
    var hostingController: UIHostingController<AnyView>!

    // viewdidLoad同上省略
}

// 子类可以这样写了
class TestVC: MyViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        hostingController.rootView = AnyView(
            MySwiftUIView("代码blablabla")
        )
    }
}

哈哈哈,天才!

但是,每次子类改rootView都要用AnyView包一下,而且还是有括号()的,太丑了。

有了!父类加个方法,专门用来修改rootView,而且支持泛型:

class MyViewController: UIViewController {
    var hostingController: UIHostingController<AnyView>!
    
    // viewdidLoad内容省略
    
    func setRootView<Content>(@ViewBuilder content: () -> Content) where Content: View {
        let c = content()
        hostingController.rootView = AnyView(c)
    }
}

// 子类可以这样写了
class TestVC: MyViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setRootView {
            MySwiftUIView("代码blablabla")
        }
    }
}

暂时看起来可以了,不用关心UIHostingController的Content是什么类型,子类随便写。

未完待续。。。