You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Objective-C项目与SwiftUI项目能否整合?及整合实现方法咨询

嘿,当然可以整合!Apple早就把Objective-C和Swift(包括SwiftUI)的互操作性做得非常完善了,这也是iOS开发里很常见的场景——毕竟很多老项目都是ObjC写的,迁移到SwiftUI不用一次性重写所有代码。下面我结合你说的日记应用场景,一步步给你讲清楚怎么实现,包括转场和数据传递的细节。

一、Objective-C 与 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];
}
二、如果暂时不想直接整合SwiftUI,Objective-C与Swift的基础互调

其实上面的方案已经包含了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

火山引擎 最新活动