Correct way to use UIViewControllerRepresentable - swift

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) {}
}

Related

Present UINavigationController from SwiftUI

I have a UIViewController that I want presented from my SwiftUI view, the issue is that I need the UIViewController wrapped in a UINavigationController, how can I present that wrapped VC from my SwiftUI View?
This is what I've tried, it works for presenting the view controller but I do not know how to wrap it in a navigation controller:
struct ComposeTakeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ComposeTakeVC {
return ComposeTakeVC(nibName: "ComposeTakeVC", bundle: nil)
}
func updateUIViewController(_ uiViewController: ComposeTakeVC, context: Context) {
}
}
In the SwiftUI view:
.fullScreenCover(isPresented: $showComposeTake, content: {
ComposeTakeView()
})
I've also tried creating a new SwiftUI view with NavigationView and tool bar, but this does not show the tool bar button:
struct ComposeTakeNavView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
ComposeTakeView()
}
.toolbar {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.foregroundColor(.black)
}
}
}
}
We can wrap it right inside representable and return just as base class, like
func makeUIViewController(context: Context) -> UIViewController {
let controller = ComposeTakeVC(nibName: "ComposeTakeVC", bundle: nil)
return UINavigationController(rootViewController: controller)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}

Lock device orientation for SwiftUI UIViewControllerRepresentable

I have a UIViewController that I need to wrap and use as a SwiftUI view. I have implemented the methods needed to lock device orientation for that view controller. When I use the UIViewController in an UIKit context, the orientation could be locked.
class ContentViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}
override var shouldAutorotate: Bool {
return false
}
}
When I wrap it as a SwiftUI view with UIViewControllerRepresentable, the orientation could not be locked. The
struct ContentWrappedView : UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ContentViewController {
return ContentViewController.init()
}
func updateUIViewController(_ uiViewController: ContentViewController, context: Context) {
}
}
Is this expected behavior? Is there a way around it?
I'll be using it in a framework and so, implementing it with AppDelegate doesn't work for me. Thanks in advance.

How can I make a generic UIViewRepresentable struct?

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.

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

Set property on ViewController in UIViewControllerRepresentable

How can I change a property on a UIViewController presented via. a UIViewControllerRepresentable ?
Sample code of how I would expect it to work, however it doesn't. How can I make it work?
(color is just a example, please don't focus on that)
class MyViewController: UIViewController {
var color: UIColor? = nil {
didSet {
guard isViewLoaded else { return }
view.layer.backgroundColor = color?.cgColor
}
}
override func viewDidLoad() {
view.layer.backgroundColor = color?.cgColor
}
}
struct MyView: UIViewControllerRepresentable {
#State private var color: UIColor?
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
let viewController = MyViewController()
viewController.color = color // always nil?
return viewController
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
uiViewController.color = color // always nil?
}
}
extension MyView {
func color(_ color: UIColor) -> MyView {
self.color = color // does nothing?
return self
}
}
struct ContentView: View {
var body: some View {
MyView()
.color(.magenta)
}
}
Here is possible approach (if you expect that color can be modified externally, as it is seen). Tested with Xcode 11.4 / iOS 13.4
struct MyView: UIViewControllerRepresentable {
#Binding var color: UIColor?
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
let viewController = MyViewController()
viewController.color = color // always nil?
return viewController
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
uiViewController.color = color // always nil?
}
}
struct ContentView: View {
#State private var color: UIColor? = .magenta
var body: some View {
MyView(color: $color)
// MyView(color: .constant(.magenta)) // alternate usage
}
}
Another solution based on idea from Asperi's answer:
struct MyView: UIViewControllerRepresentable {
private class State: ObservableObject {
var color: UIColor?
}
#Binding private var state: State
init() {
_state = .constant(State())
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
return MyViewController()
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
uiViewController.color = state.color
}
}
extension MyView {
func color(_ color: UIColor) -> MyView {
self.state.color = color
return self
}
}
Or a even simpler version, we just use #Binding for the wrapped ViewController directly
struct MyView: UIViewControllerRepresentable {
#Binding private var viewController: MyViewController
init() {
_viewController = .constant(MyViewController())
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
return viewController
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
}
}
extension MyView {
func color(_ color: UIColor) -> MyView {
self.viewController.color = color
return self
}
}