ViewRepresentable用法之Coordinator模式

從UIKit/AppKit轉到SwiftUI,其實是模式上的轉換。
UIView/NSView常以代理(delegate)接收事件的方式與界面通信,而SwiftUI通過“值”變化直接響應界面事件。
爲了實現這種“UIKit/AppKit事件”到“SwiftUI屬性”的映射,我們需要借用一個對象,用它來監聽UIKit/AppKit事件,提取有用的信息,並賦值給SwiftUI屬性。約定俗成的把這個對象叫做Coordinator。

示例一

封裝UITextView

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.font = .preferredFont(forTextStyle: .body)
        view.delegate = context.coordinator
        return view
    }

    func updateUIView(_ view: UITextView, context: Context) {
        view.text = text
    }
}

extension TextView {
    class Coordinator: NSObject, UITextViewDelegate {
        @Binding private var text: String

        init(text: Binding<String>) {
            _text = text
        }

        func textViewDidChange(_ textView: UITextView) {
            text = textView.text
        }
    }
}

//使用
struct BiographyEditView: View {
    @Binding var biography: String

    var body: some View {
        TextView(text: $biography)
            .padding(10)
            .border(Color.primary, width: 0.5)
            .padding()
            .navigationTitle("Edit your biography")
    }
}

示例二

封裝Highlightr

import SwiftUI

struct HighlightTextEditor: NSViewRepresentable {
    @Binding var code: String
    var language: String
    var size: CGSize

    func makeNSView(context: NSViewRepresentableContext<HighlightTextEditor>) -> NSTextView {
        let textStorage = CodeAttributedString()
        textStorage.language = language
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        let textContainer = NSTextContainer(size: size)
        layoutManager.addTextContainer(textContainer)
        let textView = NSTextView(frame: .init(origin: .zero, size: size), textContainer: textContainer)
        textView.string = code
        textView.delegate = context.coordinator

        return textView
    }
    
    func updateNSView(_ nsView: NSTextView, context: NSViewRepresentableContext<HighlightTextEditor>) {
        nsView.string = code
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator($code)
    }
}

extension HighlightTextEditor{
    class Coordinator: NSObject, NSTextViewDelegate{
        @Binding var code: String
        init(_ code: Binding<String>) {
            _code = code
        }

        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else {
                return
            }
            code = textView.string
        }
    }

}

struct HighlightTextEditor_Previews: PreviewProvider {
    static var previews: some View {
        HighlightTextEditor(code: .constant("let a = 123"), language: "swift", size: .init(width: 300, height: 300))
    }
}

參考資料

Importing interactive UIKit views into SwiftUI

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章