iOS: Get 'UIViewController' of 'UIButton' - ios

I need to get a reference (object) of UIViewController in sub class of UIButton. Here I've tried something but failed.
class NavigationBarButton: UIButton {
override func didMoveToSuperview() {
super.didMoveToSuperview()
var viewController: UIViewController? {
var nextResponder: UIResponder? = self
repeat {
nextResponder = nextResponder?.next
if let viewController = nextResponder as? UIViewController {
return viewController
}
} while nextResponder != nil
return nil
}
guard let vcViewController = self.viewController else { print("NavigationBarButton view controller could not found"); return }
print("handle further operations with ViewController of Button")
}
}
class FirstVC: UIViewController {
#IBOutlet weak var myButton: NavigationBarButton?
}
Result:
NavigationBarButton view controller could not found
Is there any other way, without updating UIViewController, I can get view controller reference in sub class of UIButton. Any other UIButton method can help me here, where I can get view controller of button.
Similar SO Que. but not useful for this issue: Get current UIViewController from UIButton's class

This may work for you.
class NavigationBarButton: UIButton {
override func didMoveToWindow() {
super.didMoveToWindow()
var viewController: UIViewController? {
var nextResponder: UIResponder? = self
repeat {
nextResponder = nextResponder?.next
if let viewController = nextResponder as? UIViewController {
return viewController
}
} while nextResponder != nil
return nil
}
guard let vcViewController = self.viewController else { print("NavigationBarButton view controller could not found"); return }
print("handle further operations with ViewController of Button")
}
}
class FirstVC: UIViewController {
#IBOutlet weak var myButton: NavigationBarButton?
}

Related

How can I open xib(ViewController) in xib(ViewController) when I tap Button without Navigation

I am not using storyboard.
I made customTabBarView. (I added 2 button on View).
I connected the buttons with the same function and gave the buttons a tag value.
How can I open the ViewController when I press the first button or press any button?
When I tap first Button I got this error.
Error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x28184c020> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key countryListTableView.'
terminating with uncaught exception of type NSException
SceneDelegate:
guard let windowScene = (scene as? UIWindowScene) else { return }
let countryRouter = TabBarViewController()
let window = UIWindow(windowScene: windowScene)
window.rootViewController = countryRouter
self.window = window
window.makeKeyAndVisible()
ViewControllerCountryList:
class ViewControllerCountryList: UIViewController, CountryListModule.View {
.
.
.
}
TabBarButton:
class TabBarViewController: UIViewController {
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var tabBarView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
designableTabBarView()
}
private func designableTabBarView() {
tabBarView.layer.cornerRadius = tabBarView.frame.size.height / 3
tabBarView.clipsToBounds = true
}
#IBAction func onClickTabBarButton(_ sender: UIButton) {
switch sender.tag {
case 1:
let nib = UINib(nibName: "ViewControllerCountryList", bundle: nil)
guard let countryListVC = nib.instantiate(withOwner: nil, options: nil).first as? ViewControllerCountryList else { return }
self.addChild(countryListVC)
countryList.didMove(toParent: self)
default:
break
}
}
}
Using instantiate(withOwner:options:) is not really suitable for your task.
A better method is to use init(nibName:bundle:).
This extension wraps it into an easy one-line call:
extension UIViewController {
static func loadFromNib() -> Self {
func instantiateFromNib<T: UIViewController>() -> T {
return T.init(nibName: String(describing: T.self), bundle: nil)
}
return instantiateFromNib()
}
}
With that, you can now do:
let countryListVC = ViewControllerCountryList.loadFromNib()
For your "custom tab" layout, take a look at this:
class TabBarViewController: UIViewController {
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var tabBarView: UIView!
// keep references to the loaded view controllers
var countryListVC: ViewControllerCountryList!
var someOtherVC: SomeOtherViewController!
override func viewDidLoad() {
super.viewDidLoad()
designableTabBarView()
}
private func designableTabBarView() {
tabBarView.layer.cornerRadius = tabBarView.frame.size.height / 3
tabBarView.clipsToBounds = true
}
#IBAction func onClickTabBarButton(_ sender: UIButton) {
switch sender.tag {
case 1:
// remove other VC view from content view
if someOtherVC != nil {
someOtherVC.view.removeFromSuperview()
}
// if we haven't loaded ViewControllerCountryList yet
if countryListVC == nil {
countryListVC = ViewControllerCountryList.loadFromNib()
self.addChild(countryListVC)
contentView.addSubview(countryListVC.view)
countryListVC.didMove(toParent: self)
}
// add ViewControllerCountryList view to contentView
contentView.addSubview(countryListVC.view)
countryListVC.view.frame = contentView.bounds
countryListVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
case 2:
// remove country list VC view from content view
if countryListVC != nil {
countryListVC.view.removeFromSuperview()
}
// if we haven't loaded SomeOtherViewController yet
if someOtherVC == nil {
someOtherVC = SomeOtherViewController.loadFromNib()
self.addChild(someOtherVC)
contentView.addSubview(someOtherVC.view)
someOtherVC.didMove(toParent: self)
}
// add SomeOtherViewController view to contentView
contentView.addSubview(someOtherVC.view)
someOtherVC.view.frame = contentView.bounds
someOtherVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
default:
break
}
}
}
The error is saying that you've loaded a NSObject and tried to use it as if it had a countryListTableView member. This suggests that the first item in your xib is not a ViewControllerCountryList.
If looking at the xib doesn't lead to an obvious solution, I suggest debugging by examining what is actually returned from loading the xib (rather than immediately trying to cast it).

Swift 5 UIView has no UIResponder

Here is the sample source code:
ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view = Home()
}
}
Home
class Home: UIView {
...
func parent() -> UIViewController {
var responder: UIResponder? = self.next
while responder != nil {
if let c = responder as? UIViewController { return c }
else { responder = responder?.next }
}
return nil
}
}
For the line
var responder: UIResponder? = self.next
The responder is always nil, that I cannot get the ViewController.
Something strange is that, I got no problem with the same function for all the subviews of Home.

How to get instance of view controller in UIButton class which is instantiated from storyboard

I am trying to access the instance of a viewController from the UIButton class which is in the framework. Whenever the button gets instantiated from storyboard the init? (coder aDecoder: NSCoder) gets called. But I am not able to understand how to access the `viewController from it.
I have a custom init method init(viewController: UIViewController) in UIButton class, which gives me viewController when button instantiated programmatically.
eg. let button = CustomButton(viewController: self)
But now I want to get this viewController instance when button got init by storyboard i.e. at the time of init? (coder aDecoder: NSCoder) call.
Until now, I am getting viewController object using this,
button.viewController = self
But I think this is not a good practice to get things. I need an alternative way.
Any help would be appreciated. Thanks in advance.
I think you can use this code:
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
But in general, you should think about why you need it and is there another way to do it. For example, you can just pass a delegate to the button which will be responsible for this navigation.
This Property is used to getting the ParentViewController(UIViewController) of any UIView.
extension UIResponder {
var viewController:UIViewController? {
if self.next is UIViewController {
return self.next as? UIViewController
} else {
guard self.next != nil else { return nil }
return self.next?.viewController
}
}
}
Finally, with the help of my friend "Pankaj Bawane", I got this working solution.
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
}
}

Calling textfield from another class swift

called class:
class LoginViewController: UIViewController {
let chipField: UITextField = {
........
return textField1
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(chipField)
}
}
table to be called:
class MainTableViewController: UITableViewController {
....
}
MainTableViewController call chipfield in LoginViewController
You should really use a delegate in this case, something like
Read more about delegates Here
protocol MainTableViewControllerDelegate {
func getChipFieldValue()
}
class MainTableViewController: UITableViewController {
var delegate: MainTableViewControllerDelegate?
// to get chipFieldValue self.delegate?.getChipFieldValue)
}
In LoginViewController define the function in delegate
class LoginViewController: UIViewController,MainTableViewControllerDelegate {
func getChipFieldValue() ->String {
return chipField.text
}
// later in the code when you present MainTableViewController view pass delegate to self to MainTableViewController object
// something like the MainTableViewControllerObject.delegate = self
}
From your situation all you need is just a property in MainTableViewController
class MainTableViewController: UITableViewController {
var chipFieldCopy:UITextField?
...
}
But I would suggest you rethink if you need the entire UITextField, in most situation you will only need it's text so
class MainTableViewController: UITableViewController {
var chipInfoString:String?
...
}
Is enough.
In your handleLogin() method, you can pass it into MainTableViewController like:
//let navController = UINavigationController(rootViewController: MainTableViewController()) //Replace this line
let mainTableVC = MainTableViewController()
mainTableVC.chipFieldCopy = chipField
let navController = UINavigationController(rootViewController: mainTableVC)
or like I suggest
mainTableVC.chipInfoString = chipField.text
and you can access it in MainTableViewController

Cannot use UISwitch in a Custom Cell to move to new Controller

I'm trying to use the change event on a UISwitch to move from the current ViewController to a new one. My UISwitch is registered in my CustomView for the Custom Cell in my UITableView. The action is registered an calls a class in my View Controller as below
import UIKit
public class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var operatedSwitch: UISwitch!
#IBAction func operatedSwitchChange() {
updateValveOps.valveUpdate()
}
When it gets to my class in my ViewController it calls a method in the main class which should move to my new ViewController as below
import UIKit
class updateValveOps {
class func valveUpdate() {
let valveOps = ValveOperationsController()
valveOps.ValveOpsUpdate()
}
}
class ValveOperationsController: UIViewController {
.
.
func ValveOpsUpdate() {
performSegueWithIdentifier("ValveOpsToUpdateSegue", sender: nil)
}
However, this causes a Sigabrt error. I've also tried pushing from the current view to the new View Controller but then for some reason it returns back to the calling View Controller! What am I doing wrong?
Try this in your #IBAction func operatedSwitchChange() { .. }
UIApplication.sharedApplication().keyWindow?.visibleViewController()?.performSegueWithIdentifier("ValveOpsToUpdateSegue", sender: nil)
And add this extension to your project (copy-paste this into new swift file):
public extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController?) -> UIViewController? {
if vc == nil {
return nil
}
if let navigationController = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)
} else if let tabBarController = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController)
} else {
if let presentedViewController = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController)
} else {
return vc
}
}
}
}
I managed to find a way to get this working by using Protocol. First I added a Protocol to my ValveOperationsController and referenced it as below
protocol CustomCellDelegator {
func callSegueFromCell()
}
class ValveOperationsController: UIViewController, CustomCellDelegator {
Then I added a delegate to my cell in cellForRowInIndexPath
cell.delegate = self
Then I added the method called in my Protocol in to my ViewController
func callSegueFromCell() {
performSegueWithIdentifier("ValveOpsToUpdateSegue", sender: nil )
}
Then going to my CustomTableViewCell I added my delegate
var delegate:CustomCellDelegator!
Then in the event called when the Switch changes I added the call to my Protocol
if(self.delegate != nil){ //Just to be safe.
self.delegate.callSegueFromCell()
}
When the event is called when the switch changes it calls the Protocol which passes it to my method and the Segue operates successfully

Resources