使用 Swift 中的委托模式重构 CNContactPicker UI 代码

Refactoring CNContactPicker UI Code using the delegate pattern in Swift

我已经在视图控制器中成功实现了 iOS 在 iOS10 中提供的 CNContactPickerViewController ContactsUI,因此我可以让用户 select 邀请多个联系人一个事件。我试图通过实现委托模式来减小这个单一视图控制器的大小,但我卡在了黑屏上。我查看了一些资源,并认为我正在调用委托并相应地定义协议。我有一个视图控制器 CreateEventViewController,它实现了我自定义的 ContactsToInviteDelegate。本协议如下:

protocol ContactsToInviteDelegate : class {
     //array of array of KV-pairs where inner array is {"email":"email@gmail.com", "phone": "+18965883371"}
    //array of JSON objects to upload
    func contactsToInvite(_ contactsStructure: [[String:String]])
}

我的ContactPickerViewController自定义class如下:

class ContactPickerViewController: UIViewController, CNContactPickerDelegate {
    //class variables
    let phoneNumberKit = PhoneNumberKit()
    weak var delegate: ContactsToInviteDelegate?
    var contactsToSendInvitesTo = [[String:String]]()

    func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
        contacts.forEach { contact in
            let phoneNum = contact.phoneNumbers.first
            var stringPhoneNumber = String()
            do{
                let phoneNumber = try self.phoneNumberKit.parse((phoneNum?.value.stringValue)!, withRegion: "US", ignoreType:true)
            stringPhoneNumber = "+1\(phoneNumber.adjustedNationalNumber())"
            print(stringPhoneNumber)
            }
            catch {
                print("phone number parsing error")
            }

            let contactDisplayName = contact.givenName
            print("displayName: \(contactDisplayName)" )

            let contactEmail = contact.emailAddresses.first?.value ?? ""
            print("email: \(contactEmail)")

            self.contactsToSendInvitesTo.append(["email":contactEmail as String, "phone":stringPhoneNumber])
        }
        delegate?.contactsToUpload(self.contactsToSendInvitesTo)
    }

    func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
        print("cancel contact picker")
    }

    func contactPicker(_ picker: CNContactPickerViewController,didSelectContactProperties contactProperties: [CNContactProperty]) {

    }

}

并且在 CreateEventViewController 中,当我单击邀请用户按钮并执行协议方法以尝试打印显示联系人电子邮件和 phone 号码的最终结构时,我正在调用委托将邀请发送至:

func selectContactsPicker() {
        let cnPicker = ContactPickerViewController()
        cnPicker.delegate = ContactPickerViewController() as? ContactsToInviteDelegate
        self.present(cnPicker, animated:true, completion:nil)

}

func contactsToInvite(_ contactsStructure: [[String : String]]) {
    print(contactsStructure)
}

此代码未经重构以尝试使用之前工作过的委托模式。我在一个视图控制器中拥有所有这些功能,但是由于需要所有逻辑,这个文件本身超过了 400 多行。我现在的问题是,在尝试使用委托模式重构之后,当我单击按钮触发 selectContactsPicker 时,我看到的只是黑屏。我不知道我做错了什么,但我觉得这是这个功能本身。我不太确定这个函数的主体应该是什么,以便将责任委托给正确的控制器,或者如何正确显示它。我看到的示例使用了故事板和转场,例如 this。我查看了其他使用委托的示例,但我认为我的问题有点太具体了,我不知道如何从更一般的意义上提出问题。如果我这样做了,我可能一开始就不会遇到这个问题,因为那时我可能会正确理解如何实现委托模式。

委托不必是视图控制器。当视图控制器管理需要委托的元素时,这是一种方便的模式 - 而不是实例化单独的对象,只需让视图控制器实现协议即可。

有很多方法可以管理变得太大的不守规矩的视图控制器。

一种简单的方法是使用扩展。将委托协议添加到现有视图控制器:

extension SomeViewController : CNContactPickerDelegate {

   ... implement contact picker delegate methods

}

这可以很好地划分您的源代码,使其更易于阅读。

如果您想使用一个单独的 class 实例作为委托,这也可以很容易地完成。

在同一个源文件或另一个文件中声明您的委托 class:

class MyPickerDelegate : NSObject, CNContactPickerDelegate {

   ... implement contact picker delegate methods

}

注意 class 必须继承自 NSObject,但不需要是 UIViewController。

在启动联系人选择器的代码中:

picker = CNContactPickerViewController()
self.pickerDelegate = MyPickerDelegate()
picker.delegate = self.pickerDelegate
self.present(picker, animated: true)

注意选择器视图控制器仅保留对委托的弱引用,因此您必须确保在某处保留对对象的强引用。我在这里使用 属性 pickerDelegate