Fügen Sie SwiftUI-Ansichten in vorhandene UIKit-Anwendungen ein


75

Ist es möglich, Ansichten mit SwiftUI neben einer vorhandenen UIKit-Anwendung zu erstellen?

Ich habe eine bestehende Bewerbung in Objective-C geschrieben. Ich habe mit der Migration zu Swift 5 begonnen. Ich frage mich, ob ich SwiftUI neben meinen vorhandenen UIKit .xib-Ansichten verwenden kann.

Das heißt, ich möchte einige Ansichten, die mit SwiftUI erstellt wurden, und einige andere Ansichten, die mit UIKit in derselben App erstellt wurden. Die beiden natürlich nicht mischen.

SomeObjCSwiftProject/
    SwiftUIViewController.swift
    SwiftUIView.xib
    UIKitViewController.swift
    UIKitView.xib

Nebeneinander arbeiten

Antworten:


84

edit 05/06/19: Informationen zu UIHostingController hinzugefügt, wie von @Departamento B in seiner Antwort vorgeschlagen. Credits gehen an ihn!


Verwenden von SwiftUI in UIKit

Man kann SwiftUIKomponenten in vorhandenen UIKitUmgebungen verwenden, indem man a SwiftUI Viewin eine der folgenden UIHostingControllerumschließt:

let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)

Es ist auch möglich, es zu überschreiben UIHostingControllerund an die eigenen Bedürfnisse anzupassen, z. B. durch preferredStatusBarStylemanuelles Einstellen, wenn es nicht SwiftUIwie erwartet funktioniert .

UIHostingControllerist hier dokumentiert .


Verwenden von UIKit in SwiftUI

Wenn eine vorhandene UIKitAnsicht in einer SwiftUIUmgebung UIViewRepresentableverwendet werden soll, hilft das Protokoll! Es ist hier dokumentiert und kann in diesem offiziellen Apple-Tutorial in Aktion gesehen werden .


Kompatibilität

Bitte beachten Sie, dass Sie nicht SwiftUIunter iOS-Versionen <iOS 13 verwenden können, da dies SwiftUInur unter iOS 13 und höher verfügbar ist. Weitere Informationen finden Sie in diesem Beitrag.

Wenn Sie SwiftUIin einem Projekt mit einem Ziel unter iOS 13 verwenden möchten , müssen Sie Ihre SwiftUIStrukturen mit @available(iOS 13.0.0, *)Attributen versehen.


5
UIViewRepresentablescheint eher das Gegenteil zu tun, was es ermöglicht, a UIViewzu einer SwiftUIHierarchie hinzuzufügen
Departamento B

@DepartamentoB Danke! Sie haben Recht, ich werde entsprechend bearbeiten
Fredpi

@fredpi, gibt es eine Möglichkeit, mit navigationLink einen UIViewController zu pushen? Oder müssen Sie dieses Framework weiterhin verwenden, sobald Sie SwiftUI-Ansichten in Ihrem App-Storyboard verwenden?
Fernando

2
Es ist möglich, iOS 12 und niedriger als Ziel zu verwenden, während SwiftUI verwendet wird. Natürlich wird SwiftUI nur von iOS 13 verwendet. Für niedrigere iOS-Versionen müssen Sie UIKit-Ansichten erstellen (doppelte Arbeit ...). Aber für diejenigen, die migrieren, könnte hilfreich sein. Weitere Informationen hier: stackoverflow.com/a/58372597/840742
Renatus

23

UIHostingController

Obwohl die Dokumentation für die Klasse derzeit noch nicht geschrieben wurde, UIHostingController<Content>scheint sie genau das zu sein, wonach Sie suchen: https://developer.apple.com/documentation/swiftui/uihostingcontroller

Ich habe es gerade in meiner App mit der folgenden Codezeile versucht:

let vc = UIHostingController(rootView: BenefitsSwiftUIView())

Wo BenefitsSwiftUIViewist nur der default „Hallo Welt“ Viewaus SwiftUI. Dies funktioniert genau so, wie Sie es erwarten. Es funktioniert auch, wenn Sie Unterklasse UIHostingController.


2
Sie müssen den generischen Parameter nicht explizit angeben, er kann abgeleitet werden;)
fredpi

Ja, ich habe gerade gemerkt, dass
Departamento B

Es gibt einen entsprechenden NSHostingController, der momentan - 11b2 - nicht funktioniert. Wenn Sie SwiftUI in einer Storyboard-basierten App verwenden möchten, müssen Sie Sandboxing deaktivieren und eine NSHostingView verwenden, die Sie als ContentView Ihres Fensters festlegen können. (Ja, ich verwende den gleichen Code wie mit UIHostingController. IOS funktioniert, MacOS nicht.)
green_knight

@DepartamentoB Ich kann keine Unterklasse von UIHostingController verwenden. Wissen Sie, wie man das benutzt?
Sarunw

@sarunw Das musst du nicht. Es auch funktioniert , wenn Sie eine Unterklasse, aber das Beispiel habe ich nicht.
Departamento B

19

Wenn Sie SwiftUI in einen UIKit View Controller einbetten möchten, verwenden Sie eine Container View.

class ViewController: UIViewController {
    @IBOutlet weak var theContainer: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let childView = UIHostingController(rootView: SwiftUIView())
        addChild(childView)
        childView.view.frame = theContainer.bounds
        theContainer.addSubview(childView.view)
        childView.didMove(toParent: self)
    }
}

Referenz


8

Ein Element, das ich noch nicht erwähnt habe und das Xcode 11 Beta 5 (11M382q) beinhaltet, beinhaltet das Aktualisieren der info.plist-Datei Ihrer App.

In meinem Szenario nehme ich eine vorhandene Swift & UIKit-basierte Anwendung und migriere sie vollständig zu einer iOS 13 & reinen SwiftUI-App, sodass die Abwärtskompatibilität für mich kein Problem darstellt.

Nachdem Sie die erforderlichen Änderungen an AppDelegate vorgenommen haben:

// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication,
                 configurationForConnecting connectingSceneSession: UISceneSession,
                 options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration",
                                sessionRole: connectingSceneSession.role)
}

Und Hinzufügen einer SceneDelegate-Klasse:

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: HomeList())
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

Ich hatte ein Problem, bei dem mein SceneDelegate nicht aufgerufen wurde. Dies wurde behoben, indem Folgendes in meine info.plist-Datei eingefügt wurde:

<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <false/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string></string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneStoryboardFile</key>
                <string>LaunchScreen</string>
            </dict>
        </array>
    </dict>
</dict>

Und ein Screenshot zu sehen: Geben Sie hier die Bildbeschreibung ein

Die wichtigsten Elemente, die synchron gehalten werden müssen, sind:

  • Delegieren Sie den Klassennamen, damit Xcode weiß, wo sich Ihre SceneDelegateDatei befindet
  • Konfigurationsname, damit der Aufruf in AppDelegate den richtigen laden kannUISceneConfiguration

Danach konnte ich meine neu erstellte HomeList-Ansicht (ein SwiftUI-Objekt) laden.


3

Wenn Sie eine SwiftUIAnsicht aus einem älteren Objective C-Projekt erstellen möchten, hat diese Technik für mich perfekt funktioniert.

Siehe Hinzufügen von SwiftUI zu Objective-C-Apps

Ein großes Lob an unseren Freund, der das geschrieben hat.


1
Er brachte mich dazu: "Ich habe noch nie in meinem Leben eine Zeile von Swift geschrieben!" :)
Edoardo

2

Mit dem Storyboard

Sie können die HotingViewControllerKomponente im Interface Builder verwenden:

Vorschau

Dann, wenn Sie eine einfache HotingControllerwie diese haben:

class MySwiftUIHostingController: UIHostingController<Text> {
    required init?(coder: NSCoder) {
        super.init(coder: coder, rootView: Text("Hello World"))
    }
}

Sie können es als benutzerdefinierte Klasse des Controllers festlegen:

Vorschau 2


Mit Code

let mySwiftUIHostingController = UIHostingController(rootView: Text("Hello World"))

Und dann können Sie es wie ein normaler verwenden UIViewController


Wichtige Notiz

Vergessen Sie nicht, zu importieren, SwiftUIwo immer Sie es brauchenUIHostingController


2

Wenn Sie mit Layoutproblemen konfrontiert sind, müssen Sie der Ansicht von UIHostingController Einschränkungen hinzufügen.

class ViewController: UIViewController {
    @IBOutlet weak var theContainer: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let childView = UIHostingController(rootView: SwiftUIView())
        addChild(childView)
        childView.view.frame = theContainer.bounds
        theContainer.addConstrained(subview: childView.view)
        childView.didMove(toParent: self)
    }
}

mit dieser Erweiterung:

extension UIView {
    func addConstrained(subview: UIView) {
        addSubview(subview)
        subview.translatesAutoresizingMaskIntoConstraints = false
        subview.topAnchor.constraint(equalTo: topAnchor).isActive = true
        subview.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        subview.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        subview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
}

1

Sie können sie zusammen verwenden. Sie können ‚Transfer‘ ein , UIViewum Viewdurch UIViewRepresentableKonformität. Details finden Sie im offiziellen Tutorial .

Sie sollten jedoch auch die Kompatibilität berücksichtigen.

Hier ist das Code-Snippet aus dem Protokoll Viewvon SwiftUI :

///
/// You create custom views by declaring types that conform to the `View`
/// protocol. Implement the required `body` property to provide the content
/// and behavior for your custom view.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View : _View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    /// ...
}

Es ist also nicht abwärtskompatibel.

  • iOS 13.0+
  • macOS 10.15+
  • watchOS 6.0+

1
Dies ist umgekehrt, wo ein UIView in einem View
Departamento B

1
import Foundation
    #if canImport(SwiftUI)
    import SwiftUI

internal final class SomeRouter {
    fileprivate weak var presentingViewController: UIViewController!

    function navigateToSwiftUIView() {
       if #available(iOS 13, *) {
            let hostingController = UIHostingController(rootView: contentView())  
presentingViewController?.navigationController?.pushViewController(hostingController, animated: true)
            return
        }
        //Keep the old way when not 13.
}
#endif

-3

Andere haben gezeigt, wie UIHostingController verwendet wird .

Ich kann zeigen, wie Sie einen UIViewController von SwiftUI UIViewControllerRepresentable präsentieren können :

struct YourViewControllerWrapper: UIViewControllerRepresentable {
    typealias UIViewControllerType = YourViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<YourViewControllerWrapper>) -> YourViewController {
        let storyBoard = UIStoryboard(name: "YourStoryboard", bundle: Bundle.main)
        return storyBoard.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
    }

    func updateUIViewController(_ uiViewController: YourViewController, context: UIViewControllerRepresentableContext<YourViewController>) {
        // do nothing
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.