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

visc
quelle

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.

Fredpi
quelle
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.

Departamento B.
quelle
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

Mike Lee
quelle
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.

CodeBender
quelle
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.

Snips
quelle
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

Mojtaba Hosseini
quelle
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
    }
}
Mostfa Essam
quelle
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+
Benjamin Wen
quelle
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
patilh
quelle
-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
    }
}
Nigong
quelle