📱

UIKit>SwiftUI>UIKit...の入れ子構造でUIHostingControllerに指定する親VC

に公開

この記事は TimeTree Advent Calendar 2025 の15日目の記事です。
https://qiita.com/advent-calendar/2025/timetree

こんにちは、TimeTreeでiOSアプリ開発をしています pengtaros です🐧

この記事ではUIKit > SwiftUI > UIKit ... のような入れ子構造のビューで発生したオートレイアウトのエラーとその対処方法について書きます

発生したエラーとコード

以下の階層構造を持つビューで発生しました

  1. SwiftUI.View(背景青)
SomeSwiftUIView.swift
struct SomeSwiftUIView: View {
    var body: some View {
        Text("1. SwiftUI View")
            .padding()
            .foregroundStyle(Color.white)
            .background(Color.blue)
    }
}
  1. 1をUIHostingControllerを使って表示しているUIKit.UIView(背景緑)
SomeUIViewHasSwiftUIView.swift
final class SomeUIViewHasSwiftUIView: UIView {
    
    private lazy var hostingController = UIHostingController(rootView: SomeSwiftUIView())
    
    init(parent: UIViewController?) {
        super.init(frame: .zero)
        guard let parent = parent else { return }
        
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 10
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.backgroundColor = .green
        addSubview(stack)
        
        let label = UILabel()
        label.text = "2. UIView has SwiftUI View inside"
        stack.addArrangedSubview(label)
        
        parent.addChild(hostingController)
        stack.addArrangedSubview(hostingController.view)
        hostingController.view.backgroundColor = .clear
        
        NSLayoutConstraint.activate([
            stack.centerXAnchor.constraint(equalTo: centerXAnchor),
            stack.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
        
        hostingController.didMove(toParent: parent)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

  1. 2をUIViewRepresentableを使って表示しているSwiftUI.View(背景黄)
SwiftUIViewHasUIRepresentable.swift
struct SwiftUIViewHasUIRepresentable: View {
    
    weak var parent: UIViewController?
    
    var body: some View {
        VStack {
            Text("3. SwiftUI has UIKit View inside")
            SomeUIViewHasSwiftUIViewRepresentable(parent: parent)
        }
        .frame(width: 260, height: 120)
        .background(Color.yellow)
    }
}

struct SomeUIViewHasSwiftUIViewRepresentable: UIViewRepresentable {
    
    weak var parent: UIViewController?
    
    func makeUIView(context: Context) -> SomeUIViewHasSwiftUIView {
        SomeUIViewHasSwiftUIView(parent: parent)
    }
    
    func updateUIView(_ uiView: SomeUIViewHasSwiftUIView, context: Context) {}
}
  1. 3をUIHostingControllerを使って表示しているルートとなるUIViewController(背景白)
RootViewController.swift
final class RootViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 10
        stack.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stack)
        
        let label = UILabel()
        label.text = "4. UIViewController has SwiftUI View inside"
        stack.addArrangedSubview(label)
        
        let hostingController = UIHostingController(rootView: SwiftUIViewHasUIRepresentable(parent: self))
        addChild(hostingController)
        view.addSubview(hostingController.view)
        
        let hostingView = hostingController.view!
        stack.addArrangedSubview(hostingView)
        NSLayoutConstraint.activate([
            stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])

        hostingController.didMove(toParent: self)
    }
}

発生したエラー

*** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<_TtGC7SwiftUI19UIHostingControllerV26uikit_swiftui_millefeuille15SomeSwiftUIView_: 0x107854c00> should have parent view controller:<_TtGC7SwiftUI19UIHostingControllerV26uikit_swiftui_millefeuille29SwiftUIViewHasUIRepresentable_: 0x110034000> but actual parent is:<uikit_swiftui_millefeuille.RootViewController: 0x10500d770>'
terminating due to uncaught exception of type NSException

要約すると、1. SwiftUI.View (背景青) をUIView上に表示するためのUIHostingControllerに指定された親VCが、実際の親VCと異なっているようです。

問題の部分

SomeUIViewHasSwiftUIView.swift
// 背景緑のビュー
final class SomeUIViewHasSwiftUIView: UIView {

    // 背景青のSwiftUI.ViewをUIViewで表示するためのUIHostingController
    private lazy var hostingController = UIHostingController(rootView: SomeSwiftUIView())
    
    init(parent: UIViewController?) {
        ~~
        guard let parent = parent else { return }
        // 親となるVCを引数で受け取ってセット
        parent.addChild(hostingController)
        ~~
    }

}
RootViewController.swift
// 背景白のビュー
final class RootViewController: UIViewController {
    
    override func viewDidLoad() {
        ~~
        // ここでself(RootViewController)を親として渡しているが、実際に親となっているのはこのインスタンス(UIHostingController<SwiftUIViewHasUIRepresentable>)自身
        let hostingController = UIHostingController(rootView: SwiftUIViewHasUIRepresentable(parent: self))
        ~~
    }

エラーの解決方法

rootViewはUIHostingControllerインスタンス化後にセットすることが可能です。

you can change that view later using the rootView property.

https://developer.apple.com/documentation/swiftui/uihostingcontroller

UIHostingControllerインスタンスを先に作ってrootViewにparentとして渡し、その後rootViewをUIHostingControllerにセットすることでエラーを解消できます

RootViewController.swift
final class RootViewController: UIViewController {
    
    override func viewDidLoad() {
        ~~
        let hostingController = UIHostingController<SwiftUIViewHasUIRepresentable?>(rootView: nil)
        let rootView = SwiftUIViewHasUIRepresentable(parent: hostingController)
        hostingController.rootView = rootView
        addChild(hostingController)
        ~~
    }
}

(余談)この問題に遭遇した背景

TimeTreeはサービス開始から10年が経っています。
https://timetreeapp.com/intl/ja/newsroom/2025-03-24/timetree-10th-anniversary-campaign_stickers

歴史がある巨大なコードベースを元にサービスを早く・安定してデリバリーしていくために

  • SwiftUIで新規開発・刷新する部分
  • SwiftUI登場以前にUIKitで作成した既存の資産を再利用する部分

を開発の目的に合わせて選択しています。その中でどうしてもUIKit>SwiftUI>UIKit...のような入れ子構造なる部分が出てきてしまいこのようなエラーに遭遇しました。

TimeTree Tech Blog

Discussion