How can I make a generic UIViewRepresentable struct? - swift

I want build a struct that it takes a UI type as input and it present that UI in SwiftUI, for example I have this down codes for UILabel and UIButton, I want make a generic one to free me from making an individual struct for each UI from UIKit:
struct UILabelViewRepresentable: UIViewRepresentable {
let configuration: (UILabel) -> ()
func makeUIView(context: Context) -> UILabel {
return UILabel()
}
func updateUIView(_ uiView: UILabel, context: Context) {
configuration(uiView)
}
}
struct UIButtonViewRepresentable: UIViewRepresentable {
let configuration: (UIButton) -> ()
func makeUIView(context: Context) -> UIButton {
return UIButton()
}
func updateUIView(_ uiView: UIButton, context: Context) {
configuration(uiView)
}
}
Here what I tried so far:
struct GenericUIViewRepresentable<UIViewType: UIView>: UIViewRepresentable {
let uiViewType: UIViewType
let configuration: (UIViewType) -> ()
func makeUIView(context: Context) -> UIViewType {
return uiViewType
}
func updateUIView(_ uiView: UIViewType, context: Context) {
configuration(uiView)
}
}
it makes an error when I want use it:
Cannot convert value of type 'UILabel.Type' to expected argument type 'UIView'
So I know about the error, I want make my code works and it makes me free to type all UIKit UI that I want.
use case:
struct ContentView: View {
var body: some View {
GenericUIViewRepresentable(uiViewType: UILabel, configuration: { label in
label.text = "Hello, World!"
})
}
}

You want to pass in the UIView type, not just a UIView instance. This means you want to do UIViewType.Type rather than UIViewType.
Code:
struct GenericUIViewRepresentable<UIView: UIKit.UIView>: UIViewRepresentable {
let uiViewType: UIView.Type
let configuration: (UIView) -> ()
func makeUIView(context: Context) -> UIView {
uiViewType.init()
}
func updateUIView(_ uiView: UIView, context: Context) {
configuration(uiView)
}
}
Usage:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello world!")
GenericUIViewRepresentable(uiViewType: UILabel.self) { label in
label.text = "Other text!"
}
}
}
}
Here on swift.org is where you can learn more about metatype types.

Related

Correct way to use UIViewControllerRepresentable

I usually use it this way because if I create a new object in makeUIViewController there are two objects in total. So instead of creating new object I return self. Do you think I am doing it right?
final class MyViewController: UIViewController, UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MyViewController {
return self
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {}
}
Representable must be a struct, the pattern is
struct MyView: UIViewControllerRepresentable { // << view
func makeUIViewController(context: Context) -> MyViewController {
return MyViewController() // create controller !!
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {}
}

Why does a binding in UIViewRepresentables Coordinator have a constant read value

I have been writing a UIViewRepresentable and noticing some curios effects in regards to a binding I'm passing into the view.
When I read the bindings value in the coordinator through the saved UIViewRepresentable the value is always the value that it was initialized with. Trying to update the same binding however triggers an update in the surrounding UI.
This is code produces this behavior:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView)
}
func makeCoordinator() -> Coordinator {
Coordinator(_text)
}
class Coordinator: NSObject {
#Binding var text: String
init(_ text: Binding<String>){
_text = text
}
#objc func updateText(sender: UITextField){
text=sender.text!
}
func updateUI(_ uiView: UITextField) {
uiView.text = text
}
}
}
If I hover give my updateUI method a NativeTextView parameter, and use the .text field of it through the parameter, I read the correct value and the UI works correctly:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView, view: self)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var myView: NativeTextView
init(_ view: NativeTextView){
self.myView=view
}
#objc func updateText(sender: UITextField){
myView.text=sender.text!
}
func updateUI(_ uiView: UITextField, view: NativeTextView) {
uiView.text = view.text
}
}
}
It seems that the binding retains the ability to write to the outside #State variable but does not manage to access the current states value correctly. I'm guessing that this has something to do with the recreation of the NativeTextView view when SwiftUI notices an update of the #State, but I have not been able to find any documentation that would explain this behavior.
Does anyone know why this happens?
PS: for completeness this is my ContentViews body:
ZStack {
Color.red
VStack {
Text(test)
.padding()
.onTapGesture() {
test = "Bla"
}
NativeTextView(text: $test)
}
}

Safe area issue with VNDocumentCameraViewController using UIViewControllerRepresentable

How can I extend camera scanning view to safa area while using VNDocumentCameraViewController in UIViewControllerRepresentable?
here is the code
struct ScanDocumentView: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(recognizedText: $recognizedText, parent: self)
}
func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
let documentViewController = VNDocumentCameraViewController()
documentViewController.delegate = context.coordinator
return documentViewController
}
func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {
// nothing to do here
}
}
and here is it's behavior where the bottom safe area is blank
Problem solved! just add
.ignoresSafeArea()
under
ScanDocumentView()
and that's all

View content doesn't appear in UIScrollView

I have this...
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.addSubview(UIHostingController(rootView: preview).view)
return control
}
If I put my preview view outside of my custom scroll view then I can see it. But when I add it as a subview of a UIScrollView then I can't see anything.
Have I added it correctly?
Here is the complete code for the scroll view, which I got from here.
import SwiftUI
import Foundation
struct LegacyScrollView : UIViewRepresentable {
var preview: AnyView
init(preview: AnyView) {
self.preview = preview
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.addSubview(UIHostingController(rootView: preview).view)
return control
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
}
class Coordinator: NSObject {
var control: LegacyScrollView
init(_ control: LegacyScrollView) {
self.control = control
}
#objc func handleRefreshControl(sender: UIRefreshControl) {
sender.endRefreshing()
}
}
}

Fit content mode for a custom view in SwiftUI

How can I set content mode as fit for a custom UIImageView wrapped in SwiftU?
struct CustomView: UIViewRepresentable {
func makeUIView(context: Context) -> UIImageView {
CustomUIImage()
}
func updateUIView(_ uiView: UIImageView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
CustomView()
}
}
In the code above, CustomUIImage is a subclass of UIImageView from UIKit. It's wrapped in UIViewRepresentable to integrate it with the SwiftUI framework. Calling CustomView().aspectRatio(contentMode: .fit) didn't work in this case
Here is possible approach
struct CustomView: UIViewRepresentable {
private let imageView = CustomUIImage()
init(contentMode: UIView.ContentMode = .center) {
imageView.contentMode = contentMode
}
func makeUIView(context: Context) -> UIImageView {
imageView
}
func updateUIView(_ uiView: UIImageView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
CustomView(contentMode: .scaleAspectFit)
}
}