You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UIKit迁移SwiftUI时TS28BControllerDelegate代理声明位置问题

解决方法

错误原因

你当前的代码存在两个核心问题:

  • 你将TS28BControllerDelegate的代理方法写在了全局作用域,没有绑定到遵守该协议的类实例上
  • StartDiscoverTS28B方法里的controllerTS28B = self是完全错误的写法,你应该是要给controller的delegate属性赋值,而不是把self赋值给controller实例本身

正确实现方案

SwiftUI中建议使用ObservableObject封装蓝牙相关的业务逻辑和代理回调,步骤如下:

  1. 新建继承自ObservableObject的业务类,遵守TS28BControllerDelegate协议,所有代理方法都放在这个类中实现
  2. 将蓝牙相关的实例(控制器、连接的设备、温度数据等)都放在该类中管理,需要更新UI的属性用@Published修饰
  3. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 14:09:01