Get ViewController from inherited UIView class - swift

If I have this extension:
extension UIResponder {
var parentViewController: UIViewController? {
return (self.next as? UIViewController) ?? self.next?.parentViewController
}
}
And this one:
extension UIView {
var getParentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
If I have a class like this:
class MyClass: UIView {
init() {
print("\( parentViewController == nil )")
print("\( getParentViewController == nil )")
}
}
Both results are null.
How I can get the implemented ViewController from my UIView without pass it as parameter (directly from the view)?

You can’t get the view’s next responder in its init because it hasn’t been set yet. An appropriate time would be in didMoveToWindow.
Also, in your own answer, you said “…is the easier way to get the uiviewcontroller”, as if an app has only one instance of UIViewController, but every non-trivial app has multiple view controllers.

Related

Swift, iOS: How to get Polymorphic and Decouple Coordinator from concrete type

I have these protocols:
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func mainViewController() -> UIViewController
}
And I created a MainCoordinator that conforms to this protocol and I pass a factory that allows me to decouple the coordinator from creating and capturing a concrete type so it can be polymorphic and can be used with more implementations of UIViewController either as rootViewControllers and mainMenuViewController as shown below:
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
}
start() {
guard let mainVC = factory.mainViewController() as? MainViewController, let rootViewController = rootViewController as? UINavigationViewController else { return }
mainVC.delegate = self
rootViewController.push(mainVC, animated: true)
}
As you can see, although I've created the coordinator to accept any subclass of UIViewController it has been coupled in the start function to the concrete implementation of UIViewController: MainViewController.
So my question is how to decouple it from MainViewController and have it more polymorphic?
You can pass coordinator as a parameter type in factory function and set delegate directly in factory function while creating controller instance. That way you wouldn’t have to expose controller type explicitly out of factory classes.
I came up with below approach.
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?
}
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
self.factory = factory
}
func start() {
guard let controller = factory.getViewController(delegateType: .MainCoordinator, delegateObject: self),let rootViewController = rootViewController as? UINavigationViewController else {
return
}
rootViewController.push(mainVC, animated: true)
}
}
extension MainCoordinator:DelegateCaller{
func printHello() {
print("helloo")
}
}
enum CoordinatoreTypes{
case MainCoordinator
case none
}
class Factory:UIViewControllerFactory{
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?{
switch delegateType{
case .MainCoordinator:
let controller = MainViewController()
controller.delegate = delegateObject as? MainCoordinator
return controller
case .none:
break
}
return nil
}
}
class MainViewController:UIViewController{
weak var delegate:DelegateCaller?
}
protocol DelegateCaller:AnyObject{
func printHello()
}

Why doesn't reflection work for optional types?

I am sending the viewController object to the manager class and reaching the views in the viewController with refleciton. However, if I define viewController optional, it cannot find reflection views.
class ClientManager {
var viewController : UIViewController? // not working var viewController : UIViewController is working why?
init (_ viewController: UIViewController) {
self.viewController = viewController
}
....
}
My reflection method is ;
private func prepareTarget(obj: Any, selector : String?,cellIdentifier :String?)
-> UIView?
{
let mirror = Mirror(reflecting: obj)
var result : UIView?
for (prop, val) in mirror.children {
if prop == selector {
result = val as? UIView
break
}
if val is UITableView {
let visibleCells = (val as! UITableView).visibleCells
for cell in visibleCells {
if result == nil {
result = prepareTarget(obj: cell,selector:
selector, cellIdentifier: cellIdentifier)
}
}
}
return result
}
How can I solve this problem?

How to call a function in another class? [duplicate]

This question already has an answer here:
How to identify the viewController a button has been added onto?
(1 answer)
Closed 3 years ago.
I have a problem in a TableView. This is the class which contains the TableView.
class LogViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
…
func loadLogList() – > [String] {
…
}
The list contains all Logfiles in my Log file directory. In a custom TableViewCell I insert a button which deletes the Logfile. This is the user defined tableViewCell:
class LogTableViewCell: UITableViewCell {
…
#IBAction func deletePressed(_ sender: Any) {
let fileManager = FileManager.default
do {
try fileManager.removeItem(atPath: logURL!.path)
}
catch let error as NSError {
print("Fehler: \(error)")
}
}
After pressing the button the dependent file will be removed. Now I want to refresh this list. But the Button is in the TableViewCell class and the function for refreshing the list is in the TableView class. How can I refresh the tableview and the corresponding array?
You may use Notification Observer pattern for this, but you can access the tableView of the cell with this extension:
extension UITableViewCell {
var tableView: UITableView? {
return (next as? UITableView) ?? (parentViewController as? UITableViewController)?.tableView
}
}
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
}
}
- So now:
you can simply do tableView?.reloadData() inside the cell.

Why delegate method is not called?

I am trying to notify ChatViewController that a chat was deleted in MessagesViewController using a protocol, but the delegate method implemented in ChatViewController is never called.
In the navigationController hierarchy ChatViewController is on top of MessagesViewController.
protocol MessagesViewControllerDelegate:class {
func chatWasDeletedFromDatabase(chatUID: String)
}
class MessagesViewController: UITableViewController {
weak var delegate: MessagesViewControllerDelegate?
func observeChatRemoved() {
print("it is gonna be called")
//inform ChatViewController that a chat was deleted.
self.delegate?.chatWasDeletedFromDatabase(chatUID: chat.chatUID)
print("was called here") //prints as expected
}
}
class ChatViewController: JSQMessagesViewController {
var messagesVC: MessagesViewController?
override func viewDidLoad() {
super.viewDidLoad()
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
}
}
extension ChatViewController: MessagesViewControllerDelegate {
func chatWasDeletedFromDatabase(chatUID: String) {
print("chatWasDeletedFromDatabase called") //never prints out
if self.chatSelected.chatUID == chatUID {
//popToRootViewController
}
}
It seems
weak var delegate: MessagesViewControllerDelegate?
is nil you have to set it to the ChatViewController presented instance what ever how you present it
let chat = ///
self.delegate = chat
self.navigationController?.pushViewController(chat,animated:true)
Also do
chat.messagesVC = self
as this
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
isn't the currently presented messagesVC , so comment the above 2 lines

Cell in many different ViewControllers

I have a cell which I use in many different vc because I have my app divided in different categories but all use the same cell.
Now I have a button which should trigger the event to share it via other apps like whastapp or facebook.
The problem is that depending on in which category you are you have a different view controller which will display the function.
I can make it work with one but not with 10 different vc in just one cell.
I used an extension to get the parentviewController
extension UIView {
var parentViewController: HomeViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as! HomeViewController!
}
}
return nil
}
This will obviously only work at the Home vc.
How can I work around this issue?
You can use a protocol to handle your button action in the view controller:
protocol ShareEventDelegate: class {
func didShareButtonSelected()
}
In your custom cell:
class CustomCell: UITableViewCell {
weak var shareDelegate: ShareEventDelegate?
func yourButtonAction() {
shareDelegate.didShareButtonSelected?()
}
}
Then make your ViewControllers conform to the ShareEventDelegate, for example:
extension HomeViewController: ShareEventDelegate {
func didShareButtonSelected() {
// handle your action here
}
}
And in cellForRow:
cell.shareDelegate = self