Включить представления SwiftUI в существующее приложение UIKit

Можно ли создавать представления с помощью SwiftUI рядом с существующим приложением UIKit?

У меня есть существующее приложение, написанное на Objective-C. Я начал переход на Swift 5. Мне интересно, могу ли я использовать SwiftUI вместе с моими существующими представлениями UIKit .xib.

То есть я хочу, чтобы некоторые представления, созданные с помощью SwiftUI, и некоторые другие представления, созданные с помощью UIKit, были в одном приложении. Не смешивая их, конечно.

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

Работая рядом друг с другом

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
128
0
60 312
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

edit 06.05.19: добавлена ​​информация о UIHostingController, предложенная @Departamento B в его ответе. Кредиты идут к нему!


Использование SwiftUI в UIKit

Можно использовать компоненты SwiftUI в существующих средах UIKit, обернув SwiftUIView в UIHostingController следующим образом:

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

Также возможно переопределить UIHostingController и настроить его под свои нужды, например. грамм. установив preferredStatusBarStyle вручную, если он не работает через SwiftUI, как ожидалось.

UIHostingController задокументировано здесь.


Использование UIKit в SwiftUI

Если существующее представление UIKit необходимо использовать в среде SwiftUI, протокол UIViewRepresentable поможет! Это задокументировано здесь, и его можно увидеть в действии в официальном руководстве Apple по это.


Совместимость

Обратите внимание, что вы не можете использовать SwiftUI на версиях iOS < iOS 13, так как SwiftUI доступно только на iOS 13 и выше. Дополнительную информацию см. в сообщении это.

Если вы хотите использовать SwiftUI в проекте с целью ниже iOS 13, вам нужно пометить свои SwiftUI структуры атрибутом @available(iOS 13.0.0, *).

UIViewRepresentable, похоже, делает наоборот, позволяя добавить UIView в иерархию SwiftUI
Lucas van Dongen 05.06.2019 12:27

@DepartamentoB Спасибо! Вы правы, я отредактирую соответственно

fredpi 05.06.2019 12:48

@fredpi, есть ли способ использовать navigationLink для отправки UIViewController? Или как только вы начнете использовать представления SwiftUI в раскадровке своего приложения, вам придется продолжать использовать этот фреймворк?

Fernando 17.09.2019 14:48

При использовании SwiftUI можно ориентироваться на iOS 12 и ниже. Конечно, SwiftUI будет использоваться только iOS 13, для более ранних версий iOS вам нужно будет создавать представления UIKit (дублировать работу...). Но для тех, кто мигрирует, может быть полезно. Подробнее здесь: stackoverflow.com/a/58372597/840742

Renatus 24.10.2019 14:01

Как удалить SomeSwiftUIView() из parentViewController?

Qazi Ammar 19.02.2022 14:40

Вы можете использовать их вместе. Вы можете «перенести» UIView в View с помощью UIViewRepresentable соответствия. Подробности можно найти в файле официальный учебник.

Тем не менее, вы также должны учитывать его совместимость.

Вот фрагмент кода из протокола ViewSwiftUI:

///
/// 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.
    /// ...
}

Так что обратной совместимости нет.

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

Это наоборот, когда UIView размещается в представлении.

Lucas van Dongen 05.06.2019 12:43

UIHostingController

Хотя на данный момент документация по классу не написана, UIHostingController<Content> похоже это то, что вы ищете: https://developer.apple.com/documentation/swiftui/uihostingcontroller

Я только что попробовал это в своем приложении со следующей строкой кода:

let vc = UIHostingController(rootView: BenefitsSwiftUIView())

Где BenefitsSwiftUIView — это просто «Hello World» по умолчанию View из SwiftUI. Это работает именно так, как вы ожидаете. Это также работает, если вы подкласс UIHostingController.

Вам не нужно явно указывать общий параметр, его можно вывести;)

fredpi 05.06.2019 13:00

Да, я только что понял, что

Lucas van Dongen 05.06.2019 13:03

Есть соответствующий NSHostingController, который сейчас — 11b2 — не работает. Если вы хотите использовать SwiftUI в приложении на основе раскадровки, вам нужно отключить песочницу и использовать NSHostingView, который вы можете установить в качестве содержимого вашего окна. (Да, я использую тот же код, что и с UIHostingController. iOS работает, macOS — нет.)

green_knight 21.06.2019 21:48

@DepartamentoB Я не могу использовать подкласс UIHostingController. Вы знаете, как это использовать?

sarunw 25.07.2019 12:23

@sarunw Тебе не нужно. Это также работает, если вы подкласс, но пример, который я привел, не работает.

Lucas van Dongen 25.07.2019 13:03

@DepartamentoB Я знаю, что ваш ответ не для подкласса, но я хочу знать, как заставить его работать с подклассом. Я не могу заставить его работать таким образом.

sarunw 25.07.2019 14:15

Вы можете просто создать подкласс UIHostingController? Нет ничего, что удерживает вас.

Lucas van Dongen 25.07.2019 16:43
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

Другие демонстрировали, как использовать UIHostingController.

Я могу показать, как можно представить UIViewController из SwiftUI UIViewControllerRepresentable:

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
    }
}

Один элемент, о котором я еще не упоминал, включает в себя Xcode 11 beta 5 (11M382q) и включает обновление файла info.plist вашего приложения.

Для моего сценария я беру существующее приложение на основе Swift и UIKit и полностью переношу его в приложение для iOS 13 и чистого SwiftUI, поэтому обратная совместимость меня не беспокоит.

После внесения необходимых изменений в AppDelegate:

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

И добавление в класс SceneDelegate:

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()
        }
    }
}

Я столкнулся с проблемой, когда мой SceneDelegate не вызывался. Это было исправлено путем добавления следующего в мой файл info.plist:

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

И скриншот для просмотра:

Основные элементы, которые необходимо синхронизировать:

  • Имя класса делегата, чтобы Xcode знал, где найти ваш файл SceneDelegate
  • Имя конфигурации, чтобы вызов в AppDelegate мог загрузить правильный UISceneConfiguration

После этого я смог загрузить свое недавно созданное представление HomeList (объект SwiftUI)

Если вы хотите встроить SwiftUI в контроллер представления UIKit, используйте представление контейнера.

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)
    }
}

Ссылка

Это самый полный ответ здесь. Кроме того, он работает лучше других методов для правильного размещения представления SwiftUI при использовании симулятора на iOS 15.

Jose Santos 29.09.2021 19:29

Если вы хотите создать представление SwiftUI из устаревшего проекта Objective C, то этот метод отлично сработал для меня,

См. Добавление SwiftUI в приложения Objective-C

Спасибо нашему другу, который это написал.

он достал меня: «Я никогда в жизни не написал ни строчки на Swift!» :)

Edoardo 07.04.2020 20:30

С раскадровкой

Вы можете использовать компонент HotingViewController в конструкторе интерфейсов:

Preview

Тогда, если у вас есть простой HotingController вроде этого:

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

вы можете установить его как пользовательский класс контроллера:

Preview 2


С кодом

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

И тогда вы можете использовать его как обычно UIViewController


Важная заметка

Не забудьте импортировать SwiftUI куда вам нужно UIHostingController

Это не работает, по крайней мере, с iOS 15.

heiko 07.12.2021 21:02

если вы столкнулись с проблемами макета, вы должны добавить ограничения для представления UIHostingController,

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)
    }
}

используя это расширение:

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
    }
}

Вот как я это делаю:

Создайте адаптер SwiftUI

/**
 * Adapts a SwiftUI view for use inside a UIViewController.
 */
class SwiftUIAdapter<Content> where Content : View {

    private(set) var view: Content!
    weak private(set) var parent: UIViewController!
    private(set) var uiView : WrappedView
    private var hostingController: UIHostingController<Content>

    init(view: Content, parent: UIViewController) {
        self.view = view
        self.parent = parent
        hostingController = UIHostingController(rootView: view)
        parent.addChild(hostingController)
        hostingController.didMove(toParent: parent)
        uiView = WrappedView(view: hostingController.view)
    }

    deinit {
        hostingController.removeFromParent()
        hostingController.didMove(toParent: nil)
    }

}

Добавьте в контроллер представления следующим образом:

class FeedViewController: UIViewController {
    
    var adapter : SwiftUIAdapter<FeedView>!

    override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        adapter = SwiftUIAdapter(view: FeedView(), parent: self)
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    /** Override load view to load the SwiftUI adapted view */
    override func loadView() {
        view = adapter.uiView;
    }
}

Вот код для обернутого представления

Обернутое представление использует ручную компоновку (layoutSubViews), а не автоматическую компоновку, для этого очень простого случая.

class WrappedView: UIView {

    private (set) var view: UIView!

    init(view: UIView) {
        self.view = view
        super.init(frame: CGRect.zero)
        addSubview(view)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        view.frame = bounds
    }
}

Вы можете добиться этого, просто выполнив следующие шаги...

  1. Создайте кнопку в viewController, представленную на mainStoryBoard, и импортируйте SwiftUI.

  2. Добавьте hostingViewController в mainStoryboard и перетащите переход (показать переход) с кнопки на hostingViewController.Перетащите переход из StoryBoard в SwiftUI.

  3. Добавьте файл SwiftUI в свой проект, нажав Cmd+N Создание файла SwiftUI

  4. Добавьте переход формы segueAction, представленный в mainStoryBoard, в viewController.

  5. Напишите следующие строки кода в segueAction...

@IBSegueAction func ActionMe(_ coder: NSCoder) -> UIViewController? {
    return UIHostingController(coder: coder, rootView: SWIFTUI())
}

Это, на мой взгляд, лучший ответ. Может быть немного сложнее, но это работает просто и отлично.

heiko 07.12.2021 21:23

Другие вопросы по теме