tableView.selectRow(...) doesn't work when I use UIAdaptivePresentationControllerDelegate (Swift 5) - ios

In my app I use .present(...) to show one View Controller on top of the other (these are the same VC). When I dismiss it (pull down from screen) I want to select some row in my first VC. For it I use UIAdaptivePresentationControllerDelegate in which I have print(...) and tableView.selectRow(...) function. Print works fine, but tableView.selectRow doesn't work. What could be the issue?
extension UIViewController {
class func loadFromStoryboard<T: UIViewController>() -> T {
let name = String(describing: T.self)
let storyboard = UIStoryboard(name: name, bundle: nil)
if let viewController = storyboard.instantiateInitialViewController() as? T {
return viewController
} else {
fatalError("Error: No initial view controller in \(name) stroryboard")
}
}
}
class MyTabBarController: UITabBarController {
let firstVC: FirstVC = FirstVC.loadFromStoryboard()
...
override func viewDidLoad() {
super.viewDidLoad()
loadViewControllers()
}
private func generateViewController(rootViewController: UIViewController, image: UIImage?, title: String) -> UIViewController {
let navigationVC = UINavigationController(rootViewController: rootViewController)
...
return navigationVC
}
private func loadViewControllers() {
viewControllers = [
generateViewController(rootViewController: firstVC, image: ..., title: "...")
]
}
...
private func openVC() {
let storyboard = UIStoryboard(name: "FirstVC", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "FirstVC") as! FirstVC
vc.presentationController?.delegate = vc
self.present(vc, animated: true, completion: nil)
}
...
}
//FirstVC
extension FirstVC: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("VC was dismissed")
self.tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .none)
}
}
In the console I see "VC was dismissed", but row doesn't select.
P.S. I use Xcode 12.2 and iOS 14.2.
The tableView is an IBOutlet, if it's important.

You should set:
vc.presentationController?.delegate = self
Instead of:
vc.presentationController?.delegate = vc
Because you lose delegate as soon as you dismiss vc. You should listen dismissal of FirstVC on self where you present FirstVC.
If you just miss that it's ok. But if you can't understand why I can share more details :)

Related

In IOS 14, Dismissing a Presented View Controller Not Working, But in IOS 15, and 16 Working Fine

This is How I Present ViewController,
func navigateToRegistrationPage(){
// 1
let storyboard = UIStoryboard(name: "EkycRegistrationMain", bundle:
Bundle(identifier: DK_BUNDLEIDENTIFIER))
// 2
let controller = storyboard.instantiateViewController(withIdentifier:
"SplashScreenVC") as! SplashScreenVC
let navigationController = UINavigationController(rootViewController:
controller)
// 4
self.present(navigationController, animated: true, completion: nil)
}
and This is How I Dismiss
class EkycInit: NSObject {
var initiatedVC : UIViewController?
public static let shared = EkycInit()
private override init() {
super.init()
}
public func stopEKYC() {
initiatedVC?.dismiss(animated: true, completion: nil)
}
}
clicking on Login Button ViewController Should Dismiss
#IBAction func loginButtonClicked(_ sender: UIButton) {
EkycInit.shared.stopEKYC()
}
This Code Works fine in IOS 15+

Swift Navigate from third ViewController to the first ViewController

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(identifier:
"PersonViewController") as? PersonViewController
vc?.names = persons[indexPath.row].emer!
vc?.lastnames = persons[indexPath.row].mbiemer!
vc?.delegate = self
PersonViewController.indexes = indexPath.row
self.navigationController?.pushViewController(vc!, animated: true)
}`
I have a situations like this:
First ViewController is a collectionView, the second is a viewcontroller which is allowed to add new Person when I tap a button and works perfectly. I have used delegates and Core Data for local memory.
Also the second ViewController has another button to edit person. When I press button a new viewController appears with extension UIAdaptivePresentationControllerDelegate. This viewcontroller consists of 2 buttons and 2 textfields. So when I want to press save button I want to go to the first viewcontroller (collectionview list) and when to press cancel to go back to the second viewcontroller.
Viewcontrollers are created with pushViewController method.
Please anyone help what should I use?
then in PersonViewController I call this inside button edit.
#objc func editCell(){
let vc = storyboard?.instantiateViewController(identifier:
"ModalPresentationViewController") as?
ModalPresentationViewController
navigationController?.pushViewController(vc!, animated: true)
}
Now the code in the las ViewController which is ModalViewController
#objc func savePerson(){
if editNameTextfield.text == "" || editlastNameTextfield.text == ""{
self.errorLbl.alpha = 1
}
else{
let vc = ViewController()
guard let textName = editNameTextfield.text else{
return
}
guard let textLastName = editlastNameTextfield.text else{
return
}
let index = PersonViewController.indexes
DispatchQueue.main.async {[self] in
if editDelegate != nil{
self.editDelegate!.editPerson(editedName: textName, editedLastname: textLastName, index: index)
}
}
// What should I call here??
}
}
You can use something like:
func popTo(_ type: AnyClass) {
guard let controllers = navigationController?.viewControllers else {return}
for controller in controllers {
if controller.classForCoder == type {
navigationController?.popToViewController(controller, animated: true)
break
}
}
}
And invoke the function depending on the condition as:
popTo(A.classForCoder())
EDIT: Above solution works only if ViewControllers B and C presented via a navigation controller and are in the same navigation stack.
Since you present the ViewController C modally, below answer should work:
Make these changes to your ViewController B that is presenting C:
class B: UIViewController, PresenterDelegate {
// some functions
func popToPrevious() {
navigationController.popViewController(animated: false)
}
#objc func editCell(){
let vc = storyboard?.instantiateViewController(identifier:
"ModalPresentationViewController") as?
ModalPresentationViewController
vc.presenterDelegate = self
present(vc, animated: true, completion: nil)
}
}
And in your ViewController C:
class C {
var presenterDelegate: PresenterDelegate? = nil
//
#objc func savePerson(){
//
//
dismiss(animated: true, completion: {
self.presenterDelegate?.popToPrevious()
})
}
}
Also add PresenterDelegate protocol to a new file or below ViewControllers B or C
protocol PresenterDelegate {
func popToPrevious()
}

How to reload UIViewController from another Controller in swift 5

I have 2 controllers A and Controller B . Controller A has a TableView and Controller B is a subview that when clicked opens a form and on Submit it enters data into the database. I want to reload my TableView from Controller B from the user hits submit.
Controller A
func btnQTYClickedFromcell(selectedIP: IndexPath) {
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "EnterQTYView")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert, animated: true, completion: nil)
}
```
popup(Controller B) will appear.after click on submit button ,get a dictionary as a response.
I want to update my Controller A with that response.
how should I do?Please Help
By using a simple notification
Add this on submit button action (Controller A)
NotificationCenter.default.post(name: Notification.Name("reloadTable"), object: nil)
add this Controller B viewdidload()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(
self.reloadMyTable(notification:)), name: Notification.Name("reloadTable"),
object: nil)
}
Now add this function somewhere on Controller B
#objc func reloadMyTable(notification: Notification) {
self.Table.reloadData()
/// Table is IBoutlet name of your tableview
}
OR You can use on Controller B
override func viewWillAppear(_ animated: Bool) {
self.Table.reloadData()
}
Custom Delegate // Make a custom Delegate
protocol CustomDelegate {
func response(_ res:Dictionary)
}
In VC2
class VC2 : UIViewController {
var delegate : CustomDelegate?
var resp : Dictionary? //which comes on click on button
... //Your code
#IBAction func submitButtonAction(_ sender: UIButton) {
self.delegate?.response(resp ?? [:])
}
}
VC1
class VC1 : UIViewController {
......
//MARK:- Delegate
func response(_ res:Dictionary) {
//Customize "res" as per your need.
// reload your tableView
tableView.reloadData()
}
}
.....
func btnQTYClickedFromcell(selectedIP: IndexPath) {
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let vc2 = storyboard.instantiateViewController(withIdentifier: "VC2 identifier")
.......
vc2.delegate = self
self.present(myAlert, animated: true, completion: nil)
}

How to present programmatically a view controller from a UIVIew

I am using a Class which is a subclass of MessageView (Swift Message Library) which is inherit from UIView. Inside, I have a UIButton and I want to present programmatically another ViewController through it.
Here is my code below :
import Foundation
import SwiftMessages
import UIKit
class MyClass: MessageView {
var hideBanner: (() -> Void)?
#IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
self.present(newViewController, animated: true, completion: nil)
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
hideBanner?()
}
open override func awakeFromNib() {
}
}
I have tried this, but it is not working since the UIView do not have the present method.
First get top ViewController using this. Then you can present your viewController.
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// topController now can use for present.
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
topController.present(newViewController, animated: true, completion: nil)
}
.present is a method in UIViewController class, that's the reason you cannot present view controller from UIView class.
To achieve this, get the root view controller and present the controller as follows:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let viewController = appDelegate.window!.rootViewController as! YourViewController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
viewController .present(newViewController, animated: true, completion: nil)
The iOS convention is that only a ViewControllers presents another ViewController.
So the answers above - where the View is finds the current ViewController via UIApplication.sharedApplication().keyWindow?.... will work but is very much an anti-pattern.
The preferred way would be:
Your MyClass view has presentation code only
You must have a ViewController which has a reference to this MyClass view
This ViewController has the #IBAction func tryAgainButtonPressed
From there, you can present the next ViewController
Try this #simple code.
import Foundation
import SwiftMessages
import UIKit
class MyClass: MessageView {
var hideBanner: (() -> Void)?
#IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
UIApplication.shared.keyWindow?.rootViewController?.present(newViewController, animated: true, completion: nil)
}
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
hideBanner?()
}
open override func awakeFromNib() {
}
}
Here is the example code using delegation pattern.
class YourViewController: UIViewController {
var yourView: MyClass // may be outlet
override func viewDidLoad() {
super.viewDidLoad()
yourView.delegate = self
}
}
protocol MyClassDelegate:class {
func tryAgainButtonDidPressed(sender: UIButton)
}
class MyClass: MessageView {
weak var delegate: MyClassDelegate?
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
delegate?.tryAgainButtonDidPressed(sender: sender)
}
}
You can achieve this by two ways
Protocol
By giving reference of that view controller to the view when you are initializing view
Sorry for the late reply. MessageView already provides a buttonTapHandler callback for you:
/// An optional button tap handler. The `button` is automatically
/// configured to call this tap handler on `.TouchUpInside`.
open var buttonTapHandler: ((_ button: UIButton) -> Void)?
#objc func buttonTapped(_ button: UIButton) {
buttonTapHandler?(button)
}
/// An optional button. This buttons' `.TouchUpInside` event will automatically
/// invoke the optional `buttonTapHandler`, but its fine to add other target
/// action handlers can be added.
#IBOutlet open var button: UIButton? {
didSet {
if let old = oldValue {
old.removeTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
}
if let button = button {
button.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
}
}
}
which is automatically invoked for any button you connect to the button outlet. So the recommended method for presenting another view controller is to have the presenting view controller configure the presentation logic in this callback:
messageView.tapHandler = { [weak self] in
guard let strongSelf = self else { return }
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
strongSelf.present(newViewController, animated: true, completion: nil)
}
If your view has more than one button, you can handle them all through buttonTapHandler since it takes a button argument. You just need to configure the target-action mechanism for each button:
otherButton.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
Or you can add one callback for each button by duplicating the above pattern.

How to dismiss the current ViewController and go to another View in Swift

I am new to Swift and I want to know how to dismiss the current view controller and go to another view.
My storyboard is like the following: MainMenuView -> GameViewController -> GameOverView. I want to dismiss the GameViewController to go to the GameOverView, not to the MainMenuView.
I use the following code in my MainMenuView:
#IBAction func StartButton(sender: UIButton) {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("GameViewController") as! GameViewController
self.presentViewController(nextViewController, animated:true, completion:nil)
restGame()
}
In the GameViewController, I use this code, but it doesn't dismiss the GameViewController.
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("GameOverView") as! GameOverView
self.presentViewController(nextViewController, animated:true, completion:nil)
This is My GameOverView Code :
class GameOverView: UIViewController{
// save the presenting ViewController
var presentingViewController :UIViewController! = self.presentViewController
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func ReplayButton(sender: UIButton) {
restGame()
didPressClose()
}
#IBAction func ReturnMainMenu(sender: UIButton) {
Data.GameStarted = 1
self.dismissViewControllerAnimated(false) {
// go back to MainMenuView as the eyes of the user
self.presentingViewController.dismissViewControllerAnimated(false, completion: nil);
}
/* let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("MainScene") as! MainScene
self.presentViewController(nextViewController, animated:true, completion:nil)*/
}
func restGame(){
Data.score = 0
Data.GameHolder = 3
Data.GameStarted = 1
Data.PlayerLife = 3.0
Data.BonusHolder = 30
Data.BonusTimer = 0
}
func didPressClose()
{
self.self.dismissViewControllerAnimated(true, completion:nil)
}
override func shouldAutorotate() -> Bool {
return false
}
deinit{
print("GameOverView is being deInitialized.");
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Any suggestions?
What you can do is let the GameOverView be presented, after all when you presenting it the GameViewController is below in the hierarchy, and then in your GameOverView run the following code to close both when you want to dismiss the GameOverView, like in the following way:
#IBAction func ReturnMainMenu(sender: UIButton) {
// save the presenting ViewController
var presentingViewController: UIViewController! = self.presentingViewController
self.dismissViewControllerAnimated(false) {
// go back to MainMenuView as the eyes of the user
presentingViewController.dismissViewControllerAnimated(false, completion: nil)
}
}
The above code need to be called when you want to dismiss the GameOverView.
I hope this help you.
The below code will take you to the main VC, Here's a tried and tested piece of code.
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)

Resources