UIViewRepresentable SwiftUI和UIKit之间的桥接

UIViewRepresentable是一个协议,用于创建一个SwiftUI视图,该视图包装了一个UIKit视图。通过实现UIViewRepresentable协议,我们可以在SwiftUI中使用自定义的UIKit视图,并与SwiftUI进行交互。

实现UIViewRepresentable

创建一个遵循UIViewRepresentable协议的自定义结构体,比如我们用TextField举例,创建一个TextFieldViewRepresentable,并实现该协议最基础的两个协议方法。

  • makeUIView(context:):创建并返回UIKit视图。
  • updateUIView(_:context:):更新UIKit视图。
struct TextFieldViewRepresentable: UIViewRepresentable {

  func makeUIView(context: Context) -> some UIView {
    let textField = UITextField()
    return textField
  }
  
  func updateUIView(_ uiView: UIViewType, context: Context) {

  }
}

如果在makeUIView方法中明确了要返回的UIKit控件的类型,那么方法的返回值就可以改为指定的类型,而不是some UIView,改完后,把updateUIView方法删了,再重新添加,就会发现updateUIView方法中的uiView的类型不是UIViewType了,而是我们在makeUIView方法中的返回类型。

struct TextFieldViewRepresentable: UIViewRepresentable {

  func makeUIView(context: Context) -> UITextField {
    let textField = UITextField()
    textField.backgroundColor = UIColor.gray
    return textField
  }
  
  func updateUIView(_ uiView: UITextField, context: Context) {

  }
}

在SwiftUI中调用:

struct UIViewRepresentableDemo: View {
  var body: some View {
    VStack {
      Text("Hello, World!")
      TextFieldViewRepresentable()
    }
  }
}

UIKit向SwiftUI传值

上面的代码中,我们在TextField中输入任何内容都不会显示在SwiftUI的界面上的,也就是SwiftUI拿不到TextField中输入的内容,这里我们就需要绑定一个值来传递了。

要想拿到输入的内容,我们组要一个协调员去处理TextFieldDelegate的方法,这里就需要实现makeCoordinator()方法。makeCoordinator()方法也是UIViewRepresentable的协议方法。下面在TextFieldViewRepresentable结构体里面定义一个协调员Coordinator。

struct TextFieldViewRepresentable: UIViewRepresentable {

  @Binding var text: String

  func makeUIView(context: Context) -> UITextField {
    let textField = UITextField()
    textField.backgroundColor = UIColor.gray
    textField.delegate = context.coordinator
    return textField
  }
  
  func makeCoordinator() -> Coordinator {
    return Coordinator(text: $text)
  }

  class Coordinator: NSObject, UITextFieldDelegate {
    @Binding var text: String

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

    func textFieldDidChangeSelection(_ textField: UITextField) {
      text = textField.text ?? ""
    }
  }
}

上面代码中我们创建一个类Coordinator,注意是class类型,该类实现了UITextFieldDelegate协议。

在Coordinator类中定义了一个@Binding修饰的text属性,用于绑定输入的值。在textFieldDidChangeSelection协议方法中进行赋值。

在TextFieldViewRepresentable中我们也定义了一个@Binding修饰的text属性,用于绑定SwiftUI中的@State修饰的String类型的值。

在实现的makeCoordinator()方法中,创建并返回Coordinator的具体实例,并绑定text。

在makeUIView方法中,这里我们通过context的coordinator属性能拿到刚才通过makeCoordinator()方法创建的Coordinator实例,并将UITextField的代理设置为该实例。

textField.delegate = context.coordinator

SwiftUI部分调用代码:

struct UIViewRepresentableDemo: View {
  @State private var text: String = ""

  var body: some View {
    VStack {
      Text(text)
      TextFieldViewRepresentable(text: $text)
        .frame(height: 50)
        .padding()
    }
  }
}

SwiftUI向UIKit传值

有些时候SwiftUI界面不断刷新的时候,也需要想UIKit组件传递数据,比如刚才的代码,修改一下增加了一个SwiftUI的TextField组件。

当UIKit的TextField输入的时候,SwiftUI的TextField也显示了输入的字符串,因为我们已经将text值传到SwiftUI界面了。
但是当SwiftUI的TextField在输入的时候,UIKit的TextField确没有任何显示。要实现这个,我们需要用到前面提到的updateUIView方法了,该方法在SwiftUI界面刷新的时候调用。

func updateUIView(_ uiView: UITextField, context: Context) {
  uiView.text = text
}

text是通过@Binding绑定的值,我们将这个text给UITextField即可。

定义修饰符方法

TextFieldViewRepresentable是我们定义的一个用在SwiftUI中的结构体,我们给它加一些方法,这样在SwiftUI中通过点语法就可以修改TextFieldViewRepresentable的某些属性了。

TextFieldViewRepresentable组件可能用在很多地方,不同的用处可能会设置不同的样式属性等,比如说背景色,如果在SwiftUI中直接调用.background(Color.red)方法,是不起作用的,因为没有直接修改到UITextField。
那么现在在TextFieldViewRepresentable中添加一个属性和一个方法。

var backgroundColor: UIColor?
func backgroundColor(_ color: UIColor) -> TextFieldViewRepresentable {
  var clone = self
  clone.backgroundColor = color
  return clone
}

该方法传入UIColor,并返回TextFieldViewRepresentable实例,即当前的self,这样在SwiftUI中就可以连续调用点语法了。

在makeUIView方法中,我们给UITextField设置backgroundColor。

func makeUIView(context: Context) -> UITextField {
  let textField = UITextField()
  textField.delegate = context.coordinator
  textField.placeholder = "Please input..."
  textField.borderStyle = .roundedRect
  textField.backgroundColor = backgroundColor
  return textField
}

切记自定义的方法要在SwiftUI中初始化组件后就调用,如果在系统的修饰符方法后调用则会报错的,因为其他修饰符方法返回的是some View类型,该类型下是没有我们定义的方法的。

完整代码

struct UIViewRepresentableDemo: View {
  @State private var text: String = ""

  var body: some View {
    VStack {
      Text(text)
      HStack {
        Text("SwiftUI:")
        TextField("Please input...", text: $text)
          .textFieldStyle(.roundedBorder)
      }
      HStack {
        Text("UIKit:")
        TextFieldViewRepresentable(text: $text)
          .backgroundColor(UIColor.cyan)
          .frame(height: 50)
          .padding()
      }
    }
  }
}

struct TextFieldViewRepresentable: UIViewRepresentable {

  @Binding var text: String
  var backgroundColor: UIColor?

  func makeUIView(context: Context) -> UITextField {
    let textField = UITextField()
    textField.delegate = context.coordinator
    textField.placeholder = "Please input..."
    textField.borderStyle = .roundedRect
    textField.backgroundColor = backgroundColor
    return textField
  }
  
  func updateUIView(_ uiView: UITextField, context: Context) {
    uiView.text = text
  }

  func backgroundColor(_ color: UIColor) -> TextFieldViewRepresentable {
    var clone = self
    clone.backgroundColor = color
    return clone
  }

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

  class Coordinator: NSObject, UITextFieldDelegate {
    @Binding var text: String

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

    func textFieldDidChangeSelection(_ textField: UITextField) {
      text = textField.text ?? ""
    }
  }
}

本文主要介绍了在SwiftUI中如何包装UIKit组件,当SwiftUI组件无法满足我们的App设计需求的时候,可以使用UIKit组件

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容