跳到主要内容

SwiftUI 还是 UIKit?2026 年新项目选 SwiftUI 或 UIKit 的标准

SwiftUI 还是 UIKit:2026 年新项目最低支持 iOS 17 时默认用 SwiftUI,个别控件用 UIViewRepresentable 包一层 UIKit;要支持 iOS 15 或已有大量 UIKit 代码时以 UIKit 为主,用 UIHostingController 逐步引入。

SwiftUI 还是 UIKit,2026 年新项目的结论是:最低支持 iOS 17 及以上时,界面默认用 SwiftUI,个别 SwiftUI 覆盖不到的控件用 UIViewRepresentable 包一层 UIKit。要支持 iOS 15 及以下、团队已有大量 UIKit 代码,或者核心功能是超大列表、富文本编辑、深度定制的交互,就以 UIKit 为主,新页面用 UIHostingController 逐步引入 SwiftUI。这道题不是二选一,而是定谁做主体。

下面先按场景给结论,再列两者的能力差异、各最低 iOS 版本下 SwiftUI 能用的关键能力,最后给出混用的代码。文中 API 的最低版本取自 Apple 开发者文档和 SDK 里的可用性标注,截至 2026 年 10 月。

新项目按场景该选 SwiftUI 还是 UIKit?

场景 建议 理由
全新 App,最低 iOS 17 或更高 SwiftUI 为主 @Observable、SwiftData、NavigationStack 都能直接用,状态管理和导航写法最简单
全新 App,最低要支持 iOS 15 或 16 SwiftUI 为主,但要按老写法写 iOS 15 没有 NavigationStack,iOS 16 及以下没有 @Observable 和 SwiftData,要用 NavigationView、ObservableObject、Core Data 或文件存储
最低要支持 iOS 15 以下 UIKit 为主 早期 SwiftUI 缺的能力太多,大量页面要用 if #available 写两套
上万行、自定义布局、要精细控制复用和预取的列表 UIKit 的 UICollectionView,或 SwiftUI 外壳里嵌 UIKit 列表 List 和 LazyVStack 满足一般列表,但复用、预取、滚动位置的控制粒度不如 UICollectionView
富文本、代码编辑器、聊天输入框 UIKit 的 UITextView,用 UIViewRepresentable 嵌进 SwiftUI TextEditor 从 iOS 26 起才支持绑定 AttributedString 做富文本,光标、选区、输入法的细节控制仍是 UITextView 更全
深度定制的手势、转场、和滚动联动的动画 UIKit,或在 SwiftUI 页面里局部用 UIKit UIViewPropertyAnimator、自定义转场、Core Animation 能精确到每一帧
团队已有大量 UIKit 代码 保持 UIKit 为主体,新页面用 SwiftUI 通过 UIHostingController 一页一页引入,不需要重写
Flutter、React Native、uni-app 等框架的原生部分 按框架接口用 UIKit 接入,内部可以放 SwiftUI 这些框架的原生插件、原生组件接口交给你的是 UIView 或 UIViewController,SwiftUI 页面要先用 UIHostingController 包成控制器
桌面小组件、实时活动 只能用 SwiftUI WidgetKit 的视图就是 SwiftUI 视图

小组件的完整做法见给 iOS App 加一个桌面小组件。

最低版本定多少,先看你自己 App 的用户分布。没有历史数据时可以参考 Apple 公布的数字:按 2026 年 6 月 7 日在 App Store 交易过的设备统计,79% 的 iPhone 已在 iOS 26 上,近四年发布的 iPhone 里这个比例是 86%,见 App Store 设备使用情况。

SwiftUI 和 UIKit 的能力差在哪?

方面 SwiftUI UIKit
写法 声明式,界面是状态的函数,状态变了系统自动刷新 命令式,自己在数据变化时更新视图。iOS 26 起 updateProperties() 和 layoutSubviews() 里读取的 @Observable 属性会被自动追踪,iOS 18 要在 Info.plist 加 UIObservationTrackingEnabled 开启
可用能力 随系统版本逐年增加,最低版本越高能用的越多 覆盖所有现行 iOS 版本,API 稳定,新系统主要是增量
列表 List、LazyVStack,几行代码出一个可编辑列表 UICollectionView 加组合布局和 diffable 数据源,代码多,复用和预取完全可控
导航 NavigationStack(iOS 16)按数据驱动,路径可以存下来恢复 UINavigationController 手动 push、pop,可以自定义转场
文本编辑 TextField、TextEditor,富文本绑定要 iOS 26 UITextField、UITextView,TextKit 2 从 iOS 16 起可用
自定义布局 Layout 协议(iOS 16)、Grid(iOS 16) Auto Layout、UICollectionViewLayout 子类
动画 隐式动画、matchedGeometryEffect、PhaseAnimator(iOS 17) UIView.animate、UIViewPropertyAnimator、Core Animation,可交互、可中断
数据层搭配 SwiftData 的 @Query 直接驱动视图 Core Data 加 NSFetchedResultsController,SwiftData 也能用,但要自己 fetch
多平台共用 iOS、iPadOS、macOS、watchOS、visionOS 共用的代码多 iOS、iPadOS、tvOS、visionOS,macOS 要走 Mac Catalyst,watchOS 不支持
排查界面问题 视图树由系统生成,用 Self._printChanges() 看是哪个状态触发了刷新 视图对象和代码一一对应,断点停在哪个视图很直观

两边都有自己写起来更顺的地方。SwiftUI 省掉的是同步状态和界面的样板代码,UIKit 给的是对每个视图对象的直接控制。

不同最低 iOS 版本下 SwiftUI 能用什么?

部署目标决定了你能不能直接写这些 API。低于下表版本时,编译器会报 is only available in iOS 17.0 or newer 这类错误,只能用 if #available 包起来另写一套。

最低 iOS 这一版起能用的关键能力 对选型的影响
15 AsyncImage、.task、.refreshable、.searchable、swipeActions 异步加载、下拉刷新、搜索、左滑操作都有了,常规页面可以只用 SwiftUI
16 NavigationStack、NavigationSplitView、Swift Charts、Grid、Layout 协议、ViewThatFits、presentationDetents。UIKit 侧有 UIHostingConfiguration,可以用 SwiftUI 写列表单元格 导航和自定义布局补齐,是 SwiftUI 做主体的实际起点
17 @Observable、@Bindable、SwiftData、ContentUnavailableView、scrollTargetBehavior、scrollPosition(id:)、inspector、sensoryFeedback、新版 onChange 状态管理和本地数据库换成新写法,样板代码明显减少
18 新的 Tab 写法、MeshGradient、ScrollPosition、onScrollGeometryChange,SwiftData 的 #Unique、#Index 滚动位置的读写和监听更完整
26 SwiftUI 的 WebView、TextEditor 绑定 AttributedString、glassEffect。UIKit 侧有 updateProperties() 和默认开启的自动观察追踪 富文本编辑和网页不必再包 UIKit

更细的 API 和低版本替代写法见用 AI 写 SwiftUI:提示词模板与 8 个常见坑里的版本表。SwiftData 的完整用法见 SwiftData 本地数据持久化教程。

SwiftUI 和 UIKit 怎么混用?

下面的代码在部署目标 iOS 15、Swift 5 和 Swift 6 语言模式下都编译通过。

在 UIKit 里 push 一个 SwiftUI 页面

UIHostingController 把 SwiftUI 视图包成普通的 UIViewController,可以 push、present,也可以放进 UITabBarController。原来的 delegate 回调改成闭包参数传进去。

 1import SwiftUI
 2import UIKit
 3
 4struct ProfileView: View {
 5    let name: String
 6    var onLogout: () -> Void
 7
 8    var body: some View {
 9        List {
10            Text(name)
11            Button("退出登录", role: .destructive, action: onLogout)
12        }
13        .navigationTitle("我的")
14    }
15}
16
17final class MainViewController: UIViewController {
18    func showProfile() {
19        let profile = ProfileView(name: "小林") { [weak self] in
20            self?.logout()
21        }
22        let host = UIHostingController(rootView: profile)
23        navigationController?.pushViewController(host, animated: true)
24    }
25
26    private func logout() {
27        navigationController?.popToRootViewController(animated: true)
28    }
29}

被 push 的 SwiftUI 页面直接用外层 UINavigationController 的导航栏,.navigationTitle 会显示在上面,页面里不要再套 NavigationStack。闭包里捕获控制器要写 [weak self],原因见 Swift 循环引用与内存泄漏。

在 UIKit 页面里嵌一块 SwiftUI

只想把页面的一部分换成 SwiftUI 时,把 UIHostingController 当子控制器加进来。addChild、addSubview、didMove(toParent:) 三步缺一不可,少了 addChild 时控制器层级不完整,安全区域、外观等变化传不到 SwiftUI 内容里。

 1struct StatsCard: View {
 2    var count: Int
 3    var body: some View {
 4        Text("今日完成 \(count) 项")
 5            .font(.headline)
 6            .padding()
 7    }
 8}
 9
10final class DashboardViewController: UIViewController {
11    private lazy var statsHost = UIHostingController(rootView: StatsCard(count: 0))
12
13    override func viewDidLoad() {
14        super.viewDidLoad()
15        addChild(statsHost)
16        statsHost.view.translatesAutoresizingMaskIntoConstraints = false
17        view.addSubview(statsHost.view)
18        NSLayoutConstraint.activate([
19            statsHost.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
20            statsHost.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
21            statsHost.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
22        ])
23        statsHost.didMove(toParent: self)
24    }
25
26    func update(count: Int) {
27        statsHost.rootView = StatsCard(count: count)
28    }
29}

数据变化时给 rootView 赋一个新值即可。嵌入的 SwiftUI 内容尺寸会变、要让外层 Auto Layout 跟着变时,iOS 16 起把 sizingOptions 设为 .intrinsicContentSize。

在 SwiftUI 里用 UIKit 控件

UIViewRepresentable 把 UIView 包成 SwiftUI 视图。makeUIView 只调用一次,负责创建;updateUIView 在 SwiftUI 状态变化时调用,负责把新值同步进去;UIKit 的 delegate 回调由 Coordinator 接收,再写回绑定。

 1struct RichTextView: UIViewRepresentable {
 2    @Binding var text: String
 3
 4    func makeUIView(context: Context) -> UITextView {
 5        let view = UITextView()
 6        view.font = .preferredFont(forTextStyle: .body)
 7        view.delegate = context.coordinator
 8        return view
 9    }
10
11    func updateUIView(_ view: UITextView, context: Context) {
12        if view.text != text {
13            view.text = text
14        }
15    }
16
17    func makeCoordinator() -> Coordinator {
18        Coordinator(text: $text)
19    }
20
21    final class Coordinator: NSObject, UITextViewDelegate {
22        var text: Binding<String>
23
24        init(text: Binding<String>) {
25            self.text = text
26        }
27
28        func textViewDidChange(_ textView: UITextView) {
29            text.wrappedValue = textView.text
30        }
31    }
32}
33
34struct NoteEditor: View {
35    @State private var note = ""
36
37    var body: some View {
38        RichTextView(text: $note)
39            .frame(minHeight: 200)
40    }
41}

updateUIView 里先比较再赋值。每次都直接赋 view.text 会把光标重置到末尾,输入中文时还会打断拼音输入。要包一整个控制器,例如相机、文档选择器,用 UIViewControllerRepresentable,写法相同。

在 UIKit 列表里用 SwiftUI 写单元格

列表本身留在 UICollectionView,单元格内容用 SwiftUI 写,iOS 16 起用 UIHostingConfiguration,不需要为每个单元格建一个 UIHostingController。

1@available(iOS 16.0, *)
2final class OrderCell: UICollectionViewCell {
3    func configure(title: String) {
4        contentConfiguration = UIHostingConfiguration {
5            Label(title, systemImage: "shippingbox")
6        }
7    }
8}

这是「复杂列表用 UIKit、单元格样式用 SwiftUI」的常见折中,复用和预取仍由 UICollectionView 管理。

官方说明见 Apple 文档 UIHostingController、UIViewRepresentable、UIHostingConfiguration,UIKit 的自动观察追踪见 Updating views automatically with observation tracking in UIKit。

已有 UIKit 项目怎么逐步引入 SwiftUI?

  • 从新页面开始。设置页、关于页、表单页这类状态简单的页面最适合先用 SwiftUI 写,入口处用 UIHostingController 接进现有导航。
  • 数据层先抽出来。把网络请求和业务逻辑从视图控制器里移到独立的类,UIKit 和 SwiftUI 页面共用同一份。最低 iOS 17 时这个类可以直接用 @Observable,两边都能观察它。
  • 导航保持一套。主体是 UIKit 时由 UINavigationController 管导航,SwiftUI 页面里不要再套 NavigationStack,否则会出现两层导航栏。
  • 老页面不急着重写。运行稳定的复杂页面重写收益小、风险大,等到要大改需求时再一起换。

在 KXBuild 里新建两种工程

在 Windows、Linux、macOS 上用 KXBuild 新建项目时,模板里有「iOS 应用(SwiftUI)」和「iOS 应用(Swift · UIKit)」两种,命令行 id 分别是 app_swiftui 和 app_swift,部署目标都是 iOS 15,按上面 SwiftUI 还是 UIKit 的结论选好后在「KXBuild: 编辑目标设置」的 General 页改 iOS Deployment Target,见内置模板和工程设置。

常见问题

SwiftUI 现在能完全替代 UIKit 吗?

大部分常规页面可以只用 SwiftUI 完成,但 UIKit 仍然是底层,部分能力要通过 UIViewRepresentable 或 UIViewControllerRepresentable 使用 UIKit 控件。超大列表的精细控制、复杂文本编辑、自定义转场这些场景,UIKit 依然更直接。两者在同一个 App 里混用是常态。

2026 年学 iOS 开发先学 SwiftUI 还是 UIKit?

先学 SwiftUI,能最快做出能运行的 App,新项目也多以它为主体。之后补 UIKit 的视图控制器生命周期、导航和 UITableView 或 UICollectionView,接手老项目、写跨平台框架的原生插件、包装 SwiftUI 没有的控件都会用到。

SwiftUI 的性能比 UIKit 差吗?

常规页面差别用户感觉不到,卡顿多半来自 body 里做了耗时计算、状态范围太大导致整页刷新、ForEach 的 id 不稳定。数据量很大或滚动时要精细控制复用的列表,用 UICollectionView 更容易调到稳定。先在真机上测出瓶颈再决定,不要按印象选。

老项目要不要把 UIKit 全部重写成 SwiftUI?

不需要。新页面用 SwiftUI,通过 UIHostingController 接入现有导航,老页面在需求大改时再顺带替换。全部重写要重新测试所有功能,对用户没有直接收益。

最低支持 iOS 版本定多少合适?

先看自己 App 的用户系统分布,再参考 Apple 公布的数据:截至 2026 年 6 月 7 日,79% 的 iPhone 在 iOS 26 上。定到 iOS 17 就能用 @Observable 和 SwiftData,定到 iOS 16 能用 NavigationStack,每降一个版本都要放弃一批 API 或多写一套兼容代码。