UIKit迁移SwiftUI时TS28BControllerDelegate代理声明位置问题
解决方法
错误原因
你当前的代码存在两个核心问题:
- 你将
TS28BControllerDelegate的代理方法写在了全局作用域,没有绑定到遵守该协议的类实例上 StartDiscoverTS28B方法里的controllerTS28B = self是完全错误的写法,你应该是要给controller的delegate属性赋值,而不是把self赋值给controller实例本身
正确实现方案
SwiftUI中建议使用ObservableObject封装蓝牙相关的业务逻辑和代理回调,步骤如下:
- 新建继承自
ObservableObject的业务类,遵守TS28BControllerDelegate协议,所有代理方法都放在这个类中实现 - 将蓝牙相关的实例(控制器、连接的设备、温度数据等)都放在该类中管理,需要更新UI的属性用
@Published修饰 - 在SwiftUI视图中用
@StateObject持有该类的实例,在生命周期方法中触发扫描、连接逻辑
完整修正代码
import SwiftUI import CoreBluetooth // 蓝牙业务ViewModel @MainActor class TemperatureViewModel: ObservableObject { // 对外暴露的可监听状态 @Published var currentTemperature: Float = 0 @Published var isConnected: Bool = false @Published var connectedDevice: TS28B? // 内部属性 private let myCentralManager: CBCentralManager = CBCentralManager() private let user: HealthUser = HealthUser() private var device: TS28B = TS28B() private var controllerTS28B: TS28BController = TS28BController.shared() init() { // 设置代理为当前类实例 controllerTS28B.delegate = self } // 触发同步流程 func sincroniza() { print("ENTRA EN TOMA DE TEMPERATURA TS28B") iHealthAuth() } private func iHealthAuth() { print("ENTRA EN AUTENTIFICACION DE IHEALTH TS28B") let bundle = Bundle.main guard let path = bundle.path(forResource: "com_aidicare_aidicarev3UI_ios", ofType: ".pem"), let cert = NSData(contentsOfFile: path) as? Data else { print("证书读取失败") return } IHSDKCloudUser.commandGetSDKUserInstance().commandSDKUserValidation( withLicense: cert, userDeviceAccess: { devices in print("--devices--", devices as Any) }, userValidationSuccess: { [weak self] _ in guard let self = self else { return } self.startDiscoverTS28B() }, disposeErrorBlock: { authResult in print("--UserAuthenResult--", authResult) switch authResult { case UserAuthen_InputError: print("参数错误") case UserAuthen_CertificateExpired: print("证书已过期") case UserAuthen_InvalidCertificate: print("证书无效") default: break } } ) } private func startDiscoverTS28B() { print("ENTRA EN StartDiscoverTS28B") controllerTS28B.startScan() } } // 遵守TS28BControllerDelegate协议 extension TemperatureViewModel: TS28BControllerDelegate { func controller(_ controller: TS28BController?, didDiscoverDevice device: TS28B?) { print("The agent of the device is found") connectedDevice = device controller?.connectDevice(connectedDevice) if let device = device { print("DiscoverDevice: \(device)") } } func controller(_ controller: TS28BController?, didConnectSuccessDevice device: TS28B?) { print("Successfully connected agent") isConnected = true connectedDevice = device if let device = device { print("DiscoverDevice: \(device)") } } func controller(_ controller: TS28BController?, didConnectFailDevice device: TS28B?) { print("Proxy failed to connect") isConnected = false } func controller(_ controller: TS28BController?, didDisconnectDevice device: TS28B?) { print("Disconnected proxy") isConnected = false if let device = device { print("DisConnectDevice: \(device) ") } } func controller(_ controller: TS28BController?, device: TS28B?, didUpdateTemperature value: Float, temperatureUnit unit: TemperatureUnit, measure date: Date?, measureLocation type: TemperatureType) { print("Temperature UNIDAD:", unit) print("Temperature:", value) currentTemperature = value } } // SwiftUI视图 struct TakeTemperatureTS28BUIView: View { @State private var index = 1 @StateObject private var vm = TemperatureViewModel() init(){ Theme.navigationBarColors(background: .white , titleColor: UIColor( red: CGFloat(92/255.0), green: CGFloat(203/255.0), blue: CGFloat(207/255.0), alpha: CGFloat(1.0))) } var body: some View { VStack{ ScrollView{ VStack{ VStack(){ Text("s2Tittle") .modifier(Fonts(fontName: .bold, size: 9)) .foregroundColor(Color("blackColor")) .fixedSize(horizontal: false, vertical: true) .padding(.top,10) .padding(.leading,10) .padding(.trailing,10) Image("logo") .frame(width: UIScreen.main.bounds.width / 1.5, height: 40.0) .padding() Text("当前温度:\(vm.currentTemperature)") .modifier(Fonts(fontName: .medium, size: 9)) .foregroundColor(Color("grayDarkColor")) .fixedSize(horizontal: false, vertical: true) .padding(.top,1) .padding(.bottom,10) } .background(Color("whiteColor")) .clipShape(RoundedRectangle(cornerRadius: 10.0, style: .continuous)) Button(action:{ vm.sincroniza() }){ Text(vm.isConnected ? "已连接" : "连接设备") .modifier(Fonts(fontName: .bold, size: 10)) .foregroundColor(Color("blueColor")) .fixedSize(horizontal: false, vertical: true) .offset(y: -20) } } } .padding() .padding() Spacer() MenuMain(index: self.$index) } .background(Color("backgroundColor")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { VStack { Text("s1Tittle").font(.headline) .modifier(Fonts(fontName: .light, size: 18)) .foregroundColor(Color("grayDarkColor")) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) } } } .onAppear { vm.sincroniza() } .onDisappear { // 页面销毁时断开连接 vm.controllerTS28B.disconnect(vm.connectedDevice) } } } struct TakeTemperatureTS28BUIView_Previews: PreviewProvider { static var previews: some View { NavigationView{ TakeTemperatureTS28BUIView() .navigationBarTitleDisplayMode(.inline) .accentColor(.black) .toolbar { ToolbarItem(placement: .principal) { HStack { Text("s1Tittle").font(.headline) .modifier(Fonts(fontName: .light, size: 18)) .foregroundColor(Color("grayDarkColor")) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) Spacer() } } } } } }
关键注意点
- 不要使用全局变量存储蓝牙相关实例,放在ViewModel中管理可以和页面生命周期绑定,避免内存泄漏和野指针问题
- 代理方法必须写在遵守
TS28BControllerDelegate协议的类内部,不能放在全局作用域 - 所有需要更新UI的状态值都用
@Published修饰,视图会自动响应状态变化刷新界面
内容的提问来源于stack exchange,提问作者Laura Ramírez
相关产品推荐
相关产品推荐

