I've been testing SwiftUI to see how far I can go with it. Now, I want to find out whether or not I can pass a string from SwiftUI View directly to UIViewController. And I want to display this string with UILabel. UILabel is all I have on my storyoboard.
The following is my view controller (UIViewController).
import UIKit
import SwiftUI
class HomeViewController: UIViewController {
var message = String()
#IBOutlet weak var messageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
messageLabel.text = message
}
}
struct HomeViewControllerRepresentation: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<HomeViewControllerRepresentation>) -> HomeViewController {
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeViewController") as! HomeViewController
}
func updateUIViewController(_ uiViewController: HomeViewController, context: UIViewControllerRepresentableContext<HomeViewControllerRepresentation>) {
}
}
My SwiftUI View is the following.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: HomeViewControllerRepresentation(message: "GGG")) { // Error
Text("Tap me")
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
I hoped that I could just pass a string with HomeViewControllerRepresentation. But it won't work. Is there a simple way of passing data from SwiftUI View to UIViewController? Thanks.
UIViewControllerRepresentable is not a proxy, but wrapper, so everything should be transferred manually, like
struct HomeViewControllerRepresentation: UIViewControllerRepresentable {
var message: String
func makeUIViewController(context: UIViewControllerRepresentableContext<HomeViewControllerRepresentation>) -> HomeViewController {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeViewController") as! HomeViewController
controller.message = self.message
return controller
}
func updateUIViewController(_ uiViewController: HomeViewController, context: UIViewControllerRepresentableContext<HomeViewControllerRepresentation>) {
}
}
Related
I use coordinator pattern in my app, but I have problem with instantiate view controllers. The problem is that I use different module for each tab bar controller.
So far I've used this approach
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
let id = String(describing: self)
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
return storyboard.instantiateViewController(identifier: id) as! Self
}
}
And during creation tab bar coordinator:
class MainTabBarController: UITabBarController, Storyboarded {
let main = MainCoordinator(navigationController: UINavigationController())
let calendar = CalendarCoordinator(navigationController: UINavigationController())
let chart = ChartCoordinator(navigationController: UINavigationController())
let profile = ProfileCoordinator(navigationController: UINavigationController())
override func viewDidLoad() {
super.viewDidLoad()
main.start()
calendar.start()
chart.start()
profile.start()
viewControllers = [main.navigationController, calendar.navigationController, chart.navigationController, profile.navigationController]
}
My all view controllers conform to Storyboarded Protocol:
class HomeTableViewController: UIViewController, Storyboarded {}
And coordinator for each tab bar controller looks like this
class MainCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = HomeTableViewController.instantiate()
vc.tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "home"), tag: 0)
navigationController.pushViewController(vc, animated: false)
}
}
The problem is that other tab bar controllers belong to others storyboard, not only "Main". Using instantiate() from protocol causes error. I wonder how to create protocol extension where I can initialise ViewControllers with different storyboard names, not only "Main".
Try add this:
protocol Storyboarded {
static var storyboardName: String { get }
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static var storyboardName: String {
"Main" // Default implementation
}
static func instantiate() -> Self {
let id = String(describing: self)
let storyboard = UIStoryboard(name: Self.storyboardName, bundle: Bundle.main)
return storyboard.instantiateViewController(identifier: id) as! Self
}
}
Now you can do:
extension HomeTableViewController: Storyboarded {
class var storyboardName: String {
"Home" // Other storyboard name here, overrides default implementation
}
}
I created this SwiftUI View:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
MasterView()
}.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
struct MasterView: View {
var body: some View {
Form {
Section(header: Text("Geplant")) {
Section {
NavigationLink(destination: /* How can I navigate to a Storyboard ViewController?*/) { Text("Berlin") }
}
}
}
.navigationBarTitle("Wähle Reise")
}
}
As you can read, I want to navigate at line "NavigationLink(destination: .....) to a Storyboard VC.
Before, I navigated to the SwiftUI ContentView using a UIHostingController from a UIKit VC.
Does anyone can help me?
Feel free to ask me for other Code/ Screenshots :)
Use UIViewControllerRepresentable
struct UIKitView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIKitViewController
func makeUIViewController(context: Context) -> UIKitViewController {
let sb = UIStoryboard(name: "Main", bundle: nil)
let viewController = sb.instantiateViewController(identifier: "UIKitViewController") as! UIKitViewController
return viewController
}
func updateUIViewController(_ uiViewController: UIKitViewController, context: Context) {
}
}
And then now use
NavigationLink(destination: UIKitView()) { Text("Berlin") }
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
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)
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.