Pass data from UIViewControllerRepresentable to SwiftUI - ios

I have a UIViewControllerRepresentable like this:
#ObservedObject var viewModel: HomeViewModel
typealias UIViewControllerType = ViewController
func makeUIViewController(context: Context) -> ViewController {
let storyboard = UIStoryboard(name: "ViewController", bundle: Bundle.main)
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
// ...
return viewController
}
func updateUIViewController(_ uiViewController: UIKitInboxDetail, context: Context) {
// ...
}
Inside my viewController there is a collectionView.
Now I would like to detect if my collectionView is scrolled and if so perform some actions.
So i've added this method to my ViewController:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let collectionView = scrollView as? UICollectionView {
isFirstCollectionViewScrolled = true
}
}
But i can access this data only when the view is updated and not at run tiem.
Another approach i've tried is the Coordinator, but I cant perform what I want. Any advice?
Recap: I need to perform some action in my SwiftUI View when a scroll is detected.
Update:
class HomeViewModel: ObservableObject { }
And in my viewController i'm just implementing the usual CollectionView delegates.
If i print somethng in the metod ScrollViewDidEndDegelerating it works! I just neet do something in my swiftUI View when this happened! (A boolean maybe is not the best approach since it set it to true and then it stay true)

Related

How to hide UIKit Tabbar in UIViewControllerRepresentable in SwiftUI

I am trying to accessing the Storyboard View Controller in SwiftUI by using UIViewControllerRepresentable. I want to hide the UIKit Tabbar which we applied on ItineraryViewController by using the planDetailViewController.hidesBottomBarWhenPushed = true But that solution not working.
let eventGroup : EventGroup?
func makeUIViewController(context: Context) -> UIViewController {
guard let planDetailViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ItineraryViewController") as? ItineraryViewController else {
fatalError("ViewController not implemented in storyboard")
}
planDetailViewController.userActionMode = .viewOnly
planDetailViewController.itinerary = eventGroup!.event
planDetailViewController.shouldDisableCalendarVC = true
planDetailViewController.hidesBottomBarWhenPushed = true
return planDetailViewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}```
Your UIViewControllerRepresentable won't get pushed into a UINavigationController like in UIKit so hidesBottomBarWhenPushed will not be respected.
The best way to do this is using Introspect like in this answer: https://stackoverflow.com/a/64182729/3393964

How to add Storyboard ViewController into SwiftUI Project?

I am working on my SwiftUI Project and every View is now in SwiftUI, however due to some limitations of SwiftUI I have to add Storyboard's ViewControllers into my SwiftUI project. I am trying this method,
struct AssetsListView: UIViewControllerRepresentable {
var taskID : String
public typealias UIViewControllerType = AssetsListViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<AssetsListView>) -> AssetsListViewController {
let assetsListVC = AssetsListViewController()
assetsListVC.taskID = taskID
return assetsListVC
}
func updateUIViewController(_ uiViewController: AssetsListViewController, context: UIViewControllerRepresentableContext<AssetsListView>) {
}
}
This works fine and even viewDidLoad() method of my Storyboard's ViewController calls, but I am unable to see any element on my Storyboard Screen. How can I render those elements? Just like the normal Storyboard stuff.
You just created controller by class initialiser, to instantiate it from storyboard you have to do like the following
func makeUIViewController(context:
UIViewControllerRepresentableContext<AssetsListView>) -> AssetsListViewController {
let storyboard = UIStoryboard(name: "Main", // < your storyboard name here
bundle: nil)
let assetsListVC = storyboard.instantiateViewController(identifier:
"AssetsListViewController") // < your controller storyboard id here
assetsListVC.taskID = taskID
return assetsListVC
}

Views from storyboards won't appear in Xcode Preview

I try to use Xcode Preview feature. It works well when I add views directly in code, but if I add any views via storyboard, preview won't show these views. Here is my code:
import UIKit
final class ViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl?
}
#if canImport(SwiftUI) && DEBUG
import SwiftUI
struct ViewControllerRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
return ViewController().view
}
func updateUIView(_ view: UIView, context: Context) {
}
}
#available(iOS 13.0, *)
struct ViewController_Preview: PreviewProvider {
static var previews: some View {
Group {
ViewControllerRepresentable()
}
}
}
#endif
That's my controller in simulator and storyboard:
That's how this controller looks in preview:
Set storyboardID (for example "ViewController") for your ViewController in storyboard.
Then create viewController from storyboard
func makeUIView(context: Context) -> UIView {
let viewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "ViewController")
return viewController.view
}
Use your storyboard name instead of "Main" if it's different.

Present a Modal from the View Model of a UITableViewController

I have a UITableViewController, that as a ViewModel class. I am trying to build out my application using the MVVM pattern.
My tableView has a cell that display an image, that image has a gesture recogniser that calls a method in the view model on press.
At this point I would like to present a ViewController modally, with some embedded content.
However my TableView cell conforms to UITableViewCell so I cannot call present from here.
My ViewModel does not conform to anything, so I cannot call present from there either.
How can I trigger a modal to appear, from within a UITableViewCell?
You have couple of options but I will cover solution with delegate.
The idea is to define protocol and property of that protocol in MyViewModel and make MyViewController conforming to it.
Here is how the MyViewModel could look like:
protocol MyViewModelDelegate: class {
func didTapOnCell()
}
class MyViewModel {
// Please note the delegate is weak and optional
weak var delegate: MyViewModelDelegate?
// This function handle gesture recognizer taps
#objc func handleImageViewTap() {
delegate?.didTapOnCell()
}
// Here is the rest of the ViewModel class...
}
Then in the MyViewController you set viewModel's delegate property to self and conforms to the protocol function (I'm assuming view controller references the view model instance).
class MyViewController: UITableViewController {
func setup() {
// ...
// When MyViewModel is initialised, set the delegate property to self
myViewModel.delegate = self
}
}
extension MyViewController: ViewModelDelegate {
func didTapOnCell() {
// ...
// Allocate instance of anotherViewController here and present it
self.present(anotherViewController, animated: true, completion: .none)
}
}
This way you can let know MyViewController something happened in MyViewModel and act accordingly.
Please note it's necessary to make delegate property optional to avoid retain cycles.
Add a UIWindow extension
extension UIWindow {
static var top: UIViewController? {
get {
return topViewController()
}
}
static var root: UIViewController? {
get {
return UIApplication.shared.delegate?.window??.rootViewController
}
}
static func topViewController(from viewController: UIViewController? = UIWindow.root) -> UIViewController? {
if let tabBarViewController = viewController as? UITabBarController {
return topViewController(from: tabBarViewController.selectedViewController)
} else if let navigationController = viewController as? UINavigationController {
return topViewController(from: navigationController.visibleViewController)
} else if let presentedViewController = viewController?.presentedViewController {
return topViewController(from: presentedViewController)
} else {
return viewController
}
}
}
than call this from anywhere like:
guard let topController = UIWindow.top else { return } // UIWindow.root
let youVC = theStoryboard.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
youVC.modalTransitionStyle = .crossDissolve
youVC.modalPresentationStyle = .overCurrentContext
topController.present(youVC, animated: true, completion: nil)

How to pass data between two ViewController in UIPageViewController

I have two UIViewControllers, A and B, I connect them within a UIPageViewController:
Here is how it looks in the Storyboard:
I don't know how to pass data to B from A.
Well assume you have some class (which you should have provided) like:
class MyModel {
var dataFromFirstController: Any?
var dataFromSecondController: Any?
var sharedData: Any?
}
Now you need a subclass of page view controller which is the one that controls the data so override view did load to create a model:
var myModel: MyModel!
override func viewDidLoad() {
super.viewDidLoad()
self.myModel = MyModel()
}
Now when you generate or fetch view controllers you simply assign the same model to them:
func getFirstViewController() -> UIViewController {
let controller = MyFirstController.generate()
controller.myModel = self.myModel
return controller
}
func getSecondViewController() -> UIViewController {
let controller = MySecondController.generate()
controller.myModel = self.myModel
return controller
}
Now all 3 view controllers share the same model. This is probably the easiest way of doing it but there are very many ways. The cleanest is probably using delegates which would report back to page controller that would then report back to given view controllers.
I had some difficulty finding a solution to this and came up with something myself using delegation. Suggestions are welcome
In the ViewController sending the data, define a delegate as follows:
protocol FirstVCDelegate {
func foo(someData: String)
}
class FirstViewController: UIViewController {
var delegate: FirstVCDelegate?
....
func someMethod() {
delegate?.foo("first VC")
}
}
In the PageViewController set up your View Controllers as follows:
class PageViewController: UIPageViewController, ... {
var myViewControllers = [UIViewController]()
override func viewDidLoad() {
let firstVC = storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
let secondVC = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
firstVC.delegate = secondVC
myViewControllers.append(firstVC)
myViewControllers.append(secondVC)
}
// MARK: - PageVC Delegate / Datasource
....
and finally, the receiving ViewController implements the delegate as follows:
class SecondViewController: UIViewController, FirstVCDelegate {
....
func foo(data: String) { // This method is triggered from FirstViewController's delegate?.foo("first VC")
print(data) // "first VC" will be printed
}
}
Good luck,
Aaron

Resources