I have two classes: MyViewController: UIViewController and TheView: UIView. Inside the MyViewController i have declared and object var theView: TheView and a boolean flag like var flag: Bool = false. How do I get the flag value from inside TheView class without passing if from MyViewController? I have tried methods like if let vc = self.parentViewController as? MyViewController { } but unsuccessfully.
The fact that it's difficult to get a reference to a view's associated controller should be a hint that it's bad practice. You would be better off passing the parent view controller into the view's initialiser and storing it as a weak reference.
class TheView: UIView {
weak var parentViewController: MyViewController?
init(parentViewController: MyViewController) {
self.parentViewController = parentViewController
}
}
// in MyViewController...
let view = TheView(parentViewController: self)
The best way is to declare a variable in TheView that references the parent and set this when you instantiate TheView:
var parentController: MyViewController?
Then when you instantiate theView you can attach the parent:
// Instantiate your view either view storyboard or code and then:
self.theView.parentController = self
get parentViewController from view then use this extension.
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
Get a UIView parentViewController like that.
class TheView: UIView {
func teset(){
if let vc = self.parentViewController as? ViewController {
//Sccess block
}
}
}
your ParentViewController like.
class ViewController: UITableViewController {
var theView: TheView = TheView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(theView)
}
Related
I have UIViewController and how I can to convert UIViewController to Class.swift. Class is initialized...
MainController.staticInstance.viewControllers?[1] as! Destination
MainController is class which extending UITabBarController. I want to get child controller from UITabBar and convert it to Class which parent.
Clearly example:
class MainController: UITabBarController {
override func viewDidLoad() {
(self.viewControllers?[1] as! Destination).itsMyFunction();
}
}
MAXIMUM DETAIL:
1 class
class First: UIViewController {
func itsMyFunction() {
print("Hello world!")
}
}
this is Class I attach to class in STORYBOARD!
2 class
class MainController: UITabBarController {
func override viewDidLoad() {
// Here I set index UITabBar ITEM which attach to MAIN UiTabBarController
self.selectedIndex = 0
// NOW I want to get INSTANCE CLASS First
(self.viewControllers?[1] as! First).itsMyFunction();
}
}
Can you please follow this setup example?
class TabBarViewController: UITabBarController {
let firstVC = First()
let secondVC = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
firstVC.tabBarItem = UITabBarItem(tabBarSystemItem: .search, tag: 0)
secondVC.tabBarItem = UITabBarItem(tabBarSystemItem: .more, tag: 1)
let tabBarList = [firstVC, secondVC]
viewControllers = tabBarList
if let destination = viewControllers?.first as? First {
destination.itsMyFunction()
}
}
}
It seems that you fail to get the Class instance of the controller because your viewControllers array is empty based on the code that you posted. Let me know if it worked.
That way you get a crash if the type is something different. Try to avoid using ! whenever you can.
Use a weak cast and evaluate the unwrapped type if its yours.
if let destination = destMainController.staticInstance.viewControllers?.first as? Destination {
destination.itsMyFunction()
}
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)
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
I have been searching for how the delegate works and I tried to do it in my project. Unfortunately, the delegate method I implement does not get called ever. I am trying to do a slide-out navigation panel. so what I did is that I put two uicontainerviews, one is for slide-out navigation panel and the other for main view controller
enter image description here
The code is that
For main view controller
protocol MainViewControllerDelegate {
func toggleSideMenu()
}
class MainViewController: UIViewController {
var delegate: MainViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Slide Action
#IBAction func slideMenuTapped(_ sender: UIBarButtonItem){
delegate?.toggleSideMenu()
print("Slide Menu has been tapped")
}
}
For container view controller
class ContainerVC: UIViewController {
#IBOutlet weak var SideMenuConstraint: NSLayoutConstraint!
#IBOutlet weak var slideMenuContainer: UIView!
#IBOutlet weak var mainViewContainer: UIView!
var mainViewController: MainViewController?
var isSideMenuOpened = false
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
}
}
extension ContainerVC: MainViewControllerDelegate{
func toggleSideMenu() {
print("It works")
if isSideMenuOpened{
isSideMenuOpened = false
SideMenuConstraint.constant = -260
mainViewContainer.layer.shadowOpacity = 0
} else {
isSideMenuOpened = true
SideMenuConstraint.constant = 0
mainViewContainer.layer.shadowOpacity = 0.59
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
extension UIStoryboard{
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func mainViewController() -> MainViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
}
}
Please let know what's wrong
I think the reason is that you embed your main view controller in navigation controller :
let navigationController = self.childViewControllers.last as! UINavigationController
let mainViewController = navigationController.topViewController as! MainViewController
mainViewController?.delegate = self
Here is where you got wrong:
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
this mainViewController is not the same as the child of the container view controller, so setting its delegate doesn't really do anything.
You need to first get the VC that is the child of the container view controller:
mainViewController = self.childViewControllers.last as! MainViewController
mainViewController.delegate = self
I was wondering how I could run a method from another class. For example, something like the following code:
let newVC: ScoreViewController = ScoreViewController()
newVC.makeScore()
The above code won't work for me because in makeScore(), I am changing a label's text, which I can't do with the above code because it is creating a new instance. Is there a way to call a method to be run without creating a new instance so that I can change a label's text in makeScore()?
EDIT:
How ScoreViewController is added to PageViewController:
let vc = storyboard.instantiateViewControllerWithIdentifier("Score") as! ScoreViewController
self.addChildViewController(vc)
self.scrollView.addSubview(vc.view)
I am assuming that you've some method in your FirstViewController where you're changing the score and showing it in your ScoreViewController. The delegation pattern is the possible solution for this problem. In your FirstViewController create a protocol for updating score such as:
protocol FirstVCScoreDelegate:NSObjectProtocol {
func makeScore()
}
Then inside your FirstViewController create a var for this delegate:
var delegate: FirstVCScoreDelegate
Then in your PageViewController, where you are creating the instances of the FirstViewController and ScoreViewController, set the delegate of the FirstViewController to ScoreViewController:
var firstVC: FirstViewController()
var scoreVC: ScoreViewController()
firstVC.delegate = scoreVC
And after this, in your method in the FirstViewController where the score is changing:
#IBAction func scoreChangeAction(sender: AnyObject) {
if delegate.respondsToSelector(Selector("makeScore")) {
delegate.makeScore()
}
}
This will signal the ScoreViewController to update the score. You now have to implement the delegate method inside ScoreViewController:
extension ScoreViewController: ScoreDelegate {
func makeScore() {
//update your label
}
}
I believe this will solve your problem.
UPDATE
Try this in your PageViewController's viewDidLoad: method:
override func viewDidLoad() {
super.viewDidLoad()
let mainStoryboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
let firstVC : FirstViewController = mainStoryboard.instantiateViewControllerWithIdentifier("firstVC") as FirstViewController
let scoreVC : ScoreViewController = mainStoryboard.instantiateViewControllerWithIdentifier("scoreVC") as ScoreViewController
firstVC.delegate = scoreVC
self.addChildViewController(firstVC)
self.addChildViewController(scoreVC)
self.scrollView.addSubview(firstVC.view)
self.scrollView.addSubview(firstVC.view)
}
In the PageViewController, declare a property:
class PageViewController {
var scoreViewController:ScoreViewController
// ....
}
After initializing the ScoreViewController:
let vc = storyboard.instantiateViewControllerWithIdentifier("Score") as! ScoreViewController
Hold it as a property:
self.scoreViewController = vc
Then use
self.scoreViewController.makeScore()