Objective-C项目与SwiftUI项目能否整合?及整合实现方法咨询
嘿,当然可以整合!Apple早就把Objective-C和Swift(包括SwiftUI)的互操作性做得非常完善了,这也是iOS开发里很常见的场景——毕竟很多老项目都是ObjC写的,迁移到SwiftUI不用一次性重写所有代码。下面我结合你说的日记应用场景,一步步给你讲清楚怎么实现,包括转场和数据传递的细节。
先给你吃个定心丸:完全可行,而且是官方支持的核心特性,下面分步骤来:
1. 先搞定项目的基础配置
这是互调的前提,没做好的话后面代码会报错:
- 确保项目里有桥接文件:如果你的项目是Swift项目后来加的ObjC代码,Xcode会自动提示你创建,命名一般是
[你的项目名]-Bridging-Header.h;如果没有提示,你可以手动创建,然后在Build Settings里找到Objective-C Bridging Header,填上这个文件的路径(比如$(SRCROOT)/你的项目名/你的项目名-Bridging-Header.h)。 - 在桥接文件里导入你需要调用的Objective-C头文件,比如你的日记列表控制器:
#import "DiaryListViewController.h"。 - 检查Build Settings里的
Defines Module选项,设置为YES,这是让ObjC能识别Swift代码的关键。
2. 在SwiftUI中嵌入Objective-C的UIViewController(日记列表页面)
SwiftUI调用UIKit(包括ObjC写的UIKit控制器)的标准姿势就是用UIViewControllerRepresentable协议做包装,相当于给ObjC的控制器套个SwiftUI能识别的“壳”。比如你的日记列表是ObjC写的DiaryListViewController,我们可以这么包装:
struct DiaryListVCWrapper: UIViewControllerRepresentable { // 从SwiftUI传递给ObjC控制器的初始数据(比如已有的日记列表) var initialDiaries: [Diary]? // 用@Binding接收SwiftUI的导航状态(比如是否要跳转到创建页面) @Binding var shouldShowCreatePage: Bool // 用来传递选中的日记(比如要编辑的内容) @Binding var selectedDiary: Diary? func makeUIViewController(context: Context) -> DiaryListViewController { let listVC = DiaryListViewController() // 把初始数据传给ObjC控制器(前提是ObjC控制器有对应的属性) if let diaries = initialDiaries { listVC.diaryEntries = diaries } // 设置回调block:当用户在ObjC的列表里点击某条日记时,通知SwiftUI listVC.onDiarySelected = { diary in self.selectedDiary = diary self.shouldShowCreatePage = true } return listVC } func updateUIViewController(_ uiViewController: DiaryListViewController, context: Context) { // 当SwiftUI的状态变化时,同步更新ObjC控制器的内容 if let diaries = initialDiaries { uiViewController.diaryEntries = diaries uiViewController.tableView.reloadData() } } }
然后你就可以在SwiftUI的主视图里直接用这个包装好的控制器了:
struct MainView: View { @StateObject private var diaryStore = DiaryStore() // 管理所有日记数据的ObservableObject @State private var showCreatePage = false @State private var selectedDiary: Diary? var body: some View { NavigationStack { // 直接嵌入ObjC的日记列表页面 DiaryListVCWrapper( initialDiaries: diaryStore.allDiaries, shouldShowCreatePage: $showCreatePage, selectedDiary: $selectedDiary ) .navigationTitle("我的日记") .toolbar { Button("新建日记") { showCreatePage = true } } // 跳转SwiftUI的创建/编辑页面 .sheet(isPresented: $showCreatePage) { DiaryCreateView( diaryStore: diaryStore, editDiary: selectedDiary ) } } } }
3. 视图控制器间的转场(ObjC ↔ SwiftUI)
分两种常见场景:
场景1:从SwiftUI跳转到ObjC页面
除了上面的嵌入方式,如果你需要模态或者导航跳转ObjC的页面,同样用UIViewControllerRepresentable包装,然后用SwiftUI的sheet或者navigationDestination触发即可,和上面的逻辑一致。
场景2:从ObjC页面跳转到SwiftUI页面
核心是用回调block传递事件:
首先在ObjC的头文件里声明一个block属性:
// DiaryListViewController.h #import <UIKit/UIKit.h> @class Diary; // 定义回调block的类型 typedef void(^OnDiarySelectedBlock)(Diary *selectedDiary); @interface DiaryListViewController : UIViewController @property (nonatomic, strong) NSArray<Diary *> *diaryEntries; @property (nonatomic, copy) OnDiarySelectedBlock onDiarySelected; @end
然后在ObjC的实现里,当用户点击列表项时调用这个block:
// DiaryListViewController.m - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Diary *selectedDiary = self.diaryEntries[indexPath.row]; // 如果回调存在,就触发它,把选中的日记传出去 if (self.onDiarySelected) { self.onDiarySelected(selectedDiary); } [tableView deselectRowAtIndexPath:indexPath animated:YES]; }
这样SwiftUI那边就能收到事件,触发跳转了,就是我们上面包装器里的逻辑。
4. 数据传递(ObjC ↔ SwiftUI)
数据传递的方式有几种,根据你的场景选:
方式1:共享模型
你可以用Objective-C定义模型类(比如Diary),然后在桥接文件导入头文件,Swift里就能直接使用;反过来,如果你用Swift定义模型,只要给类和属性加上@objc修饰,ObjC也能调用。
比如ObjC的Diary模型:
// Diary.h #import <Foundation/Foundation.h> @interface Diary : NSObject @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *content; @property (nonatomic, strong) NSDate *createDate; - (instancetype)initWithTitle:(NSString *)title content:(NSString *)content createDate:(NSDate *)createDate; @end
Swift里可以直接创建Diary实例,传递给ObjC控制器,或者接收ObjC传过来的Diary对象。
方式2:用ObservableObject同步状态
如果你的SwiftUI页面用ObservableObject管理数据(比如上面的DiaryStore),当SwiftUI创建了新日记,allDiaries数组更新时,包装器的updateUIViewController方法会自动触发,你可以在这个方法里刷新ObjC的tableView,实现数据同步。
方式3:NSNotificationCenter全局通知
如果是跨组件的全局数据更新,比如SwiftUI创建新日记后,要通知ObjC的列表页面刷新,可以用通知:
Swift里发通知:
NotificationCenter.default.post( name: NSNotification.Name("DiaryCreated"), object: newDiary )
ObjC里监听并处理:
// 在DiaryListViewController的viewDidLoad里 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshDiaryList:) name:@"DiaryCreated" object:nil]; // 实现刷新方法 - (void)refreshDiaryList:(NSNotification *)notification { Diary *newDiary = notification.object; [self.diaryEntries addObject:newDiary]; [self.tableView reloadData]; } // 记得在dealloc里移除观察者 - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
其实上面的方案已经包含了ObjC和Swift的整合,但如果只是纯ObjC和Swift代码互调(不用SwiftUI),核心还是桥接文件:
- Swift调用ObjC:在桥接文件导入ObjC的头文件,然后直接使用类和方法,就像使用Swift类一样。
- ObjC调用Swift:确保
Defines Module是YES,然后在ObjC文件里导入自动生成的头文件[你的项目名]-Swift.h,就可以调用Swift里用@objc修饰的类和方法。
比如Swift里的工具类:
@objc class DiaryManager: NSObject { @objc static let shared = DiaryManager() @objc func saveDiary(_ diary: Diary) { // 保存日记的逻辑 } }
ObjC里调用:
#import "MyDiaryApp-Swift.h" [[DiaryManager shared] saveDiary:newDiary];
完全不用担心整合的问题,Apple官方对Objective-C和Swift(包括SwiftUI)的互操作性支持非常完善,你可以逐步把旧的ObjC模块整合到新的SwiftUI项目里,不用一次性重写所有代码。按照上面的步骤,结合你的日记应用场景,应该能顺利实现需求。
内容的提问来源于stack exchange,提问作者Ryan Jin




