I have a form the user is filling out, and I present a modal with images for them to choose from. I am attempting to set a UIImage in the original controller from the modal, but the problem is it's nil.
What is the best way to set it? The viewDidLoad doesn't seem to trigger on dismissal of a modal, which is how I'm getting rid of it, and neither does the viewWillAppear. When can I set the image?
My Code right now:
//Collection view choosing deal background
#IBOutlet var collectionView: UICollectionView!
var chosenNumber: Int?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if(self.restorationIdentifier == "NewDeal") {
if(chosenNumber != nil) {
newDealBackgroundImage.image = UIImage(named: "food_\(chosenNumber!)")
}
}
}
extension BusinessOwnerVC: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
//self.newDealBackgroundImage.image = UIImage(named: "food_\(indexPath.row + 1)")!
chosenNumber = indexPath.row + 1
self.dismiss(animated: true, completion: nil)
}
}
If your presentation style is fullScreen … then your willAppear will be called
navigationController.modalPresentationStyle = .fullScreen
but if you dont want full screen presentation Make yourself the presentation controller's delegate and override presentationControllerDidDismiss(_:).
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear")
}
#IBAction func show(_ sender: Any) {
let newViewController = NewViewController()
//set delegate of UIAdaptivePresentationControllerDelegate to self
newViewController.presentationController?.delegate = self
present(newViewController, animated: true, completion: nil)
}
}
extension UIViewController: UIAdaptivePresentationControllerDelegate {
public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
if #available(iOS 13, *) {
//Call viewWillAppear only in iOS 13
viewWillAppear(true)
}
}
}
After much experimentation, here's what I found works for me. All I wanted was to access and update a view controller (of the same kind) under a modal I am presenting. Turns out my previous VC was still active, so all I had to do was pass a reference to that VC into the modal, and then I could change its properties.
let modal = UIStoryboard.init(name: "BusinessOwner", bundle: nil).instantiateViewController(withIdentifier: "DealBackgroundSelection") as! BusinessOwnerVC
modal.newDealView = self // This is a weak var of BusinessOwnerVC? declared in the view controller itself
present(modal, animated: true, completion: nil)
And then when I was ready to dismiss that modal:
self.newDealView!.updateDealImage(number: indexPath.row + 1)
updateDealImage was just a function I created to handle all the update. And that works for me.
Related
For example, I have two viewControllers named A and B. I move to "B view controller" after present method is called. I set some values through UserDefaults.stnadard on "B viewController", and I come back to "A view controller" after dismiss method is called. "A viewController" have to show modified values, but it doesn't show the modified ones. I tried calling viewDidAppear() method on "A viewController", but it didn't work. I tried calling viewDidLoad() method, and it worked well, but I heard that it's not a good way to call the method directly. System can only call it. So, I don't want to call it myself. How should I refresh "A view controller"?
From iOS 13.0 and above, Apple has changed the way viewControllers are presented by default. So, viewWillAppear in ViewControllerA will not be called after dimissing ViewControllerB.
One way for making sure the viewWillAppear method will be called after dismissing ViewControllerB is presenting the viewController setting modalPresentationStyle to fullScreen as:
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ViewControllerB") as! ViewControllerB
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)
Sample Code:
ViewControllerA.swift
class ViewControllerA: UIViewController {
private var username: String? {
UserDefaults.standard.string(forKey: "USERNAME")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(username) //set this to desired view
}
#IBAction func goToViewControllerB(_ sender: UIButton) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ViewControllerB") as! ViewControllerB
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true, completion: nil)
}
}
ViewControllerB.swift
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set("John", forKey: "USERNAME")
}
#IBAction func dismiss(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
If you don't want to change the modalPresentationStyle to fullScreen then you can use closures for passing data to ViewControllerA when you're dismissing ViewControllerB.
I think NotificationCenter or prototype would help get this done.
NotificationCenter example
class ViewControllerA: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(testFunc), name: NSNotification.Name(rawValue: "PeformAfterPresenting"), object: nil)
}
#objc func testFunc() {
//TODO: your task
}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismiss(_ sender: Any) {
NotificationCenter.default.post(name: "PeformAfterPresenting"), object: nil, userInfo: dataDict)
self.dismiss(animated: true, completion: nil)
}
}
A solid way to handle this would be to set up a protocol & delegate, and call it on ViewControllerB's viewWillDisappear.
In ViewControllerB, set up protocol and delegate property:
protocol ViewControllerBDelegate: AnyObject {
func refreshUserDefaults()
}
weak var delegate: ViewControllerBDelegate?
Also in ViewControllerB, call the method in viewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.delegate?.refreshUserDefaults()
}
Then in ViewControllerA, before you present ViewControllerB, assign ViewControllerA as delegate:
let vc = ViewControllerB()
vc.delegate = self
// present as desired
Also in ViewControllerA, conform to ViewControllerB's protocol:
extension ViewControllerA: ViewControllerBDelegate {
func refreshUserDefaults() {
// do whatever you need here
}
}
I have this strange problem with iOS 13 and its new sheet cards style modal presentation.
From ViewController1 I modal present ViewController2 embedded in a NavigationController, and everything works fine.
From ViewController2, I then modal present ViewController3 embedded in a NavigationController, and I get the Right Bar Button offset.
Here is a video of the problem: does anybody have a fix?
Main View Controller
import UIKit
let vc1identifier = "vc1identifier"
let vc2identifier = "vc2identifier"
class ViewController: UIViewController {
#IBAction func tap1(_ sender: UIButton) {
if let navigation = self.storyboard?.instantiateViewController(withIdentifier: vc1identifier) as? UINavigationController,
let nextVC = navigation.contentViewController as? UnoViewController {
//self.navigationController?.pushViewController(nextVC, animated: true)
self.present(navigation, animated: true, completion: nil)
}
}
}
extension UIViewController {
var contentViewController: UIViewController {
if let navcon = self as? UINavigationController {
return navcon.visibleViewController!
} else {
return self
}
}
}
Second View Controller
import UIKit
class UnoViewController: UIViewController {
#IBOutlet weak var barButton: UIBarButtonItem!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
barButton.title = "GotoVC2"
}
#IBAction func pressThis(_ sender: UIBarButtonItem) {
if let navigation = self.storyboard?.instantiateViewController(withIdentifier: vc2identifier) as? UINavigationController,
let nextVC = navigation.contentViewController as? DueViewController {
self.present(navigation, animated: true, completion: nil)
}
}
}
I came across the same issue.
Solution is easy, you just need to tell navigationbar it needs layout like this
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 13.0, *) {
navigationController?.navigationBar.setNeedsLayout()
}
}
A new UIView is displayed over the old one. While the UIViewController is presenting I want the background to be default. But as soon as it finishes presenting the UIView I want the background to turn lightGrey and the alpha should change from 0 to 0.6.
I know how to change the background and the alpha for my View, but how can I do these changes from the moment the UIViewController finished displaying?
Presenting UIViewController:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let user = indexPath.row
OperationQueue.main.addOperation {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier :"ShowProfileView") as! showProfileViewController
viewController.username = self.usernameList[user]
self.present(viewController, animated: true)
}
}
UIViewController:
class showProfileViewController: UIViewController {
#IBOutlet weak var ViewController: UIView!
#IBOutlet weak var profileLabel: UILabelX!
var username = String()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
profileLabel.text = username
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
ViewController.layer.cornerRadius = 20
ViewController.layer.masksToBounds = true
}
/*func updateProfileView() {
view.backgroundColor = UIColor.lightGray.withAlphaComponent(0.6)
view.isOpaque = false
}*/
}
Hi I have a popover view that is showing an array. I am wondering if there is a way that I could somehow segue back which item's in the array are selected.
#IBAction func popOverButton(_ sender: UIButton)
{
let controller = TableViewController()
//This is just a regular tableViewController nothing special
controller.modalPresentationStyle = .popover
// configure the Popover presentation controller
let popController: UIPopoverPresentationController? = controller.popoverPresentationController
popController?.permittedArrowDirections = [.down]
popController?.delegate = self
popController?.sourceView = sender
popController?.sourceRect = sender.bounds
popController?.backgroundColor = .white
self.parent?.present(controller, animated: true, completion: { })
}
Here is what it looks like
Any help is appreciated thanks
Easiest way is to create a delegate and when a cell is selected pass the selection back to the presenting view controller. Then setting up the UITableViewDelegate method didSelectRowAtIndexPath to call the delegate method. Something like this:
#protocol PopoverOptionSelectionDelegate {
func itemSelected(item:String);
}
Implement the method in your presenting VC
class PresnetingViewController, PopoverOptionSelectionDelegate {
#IBAction func popOverButton(_ sender: UIButton) {
let controller = TableViewController()
controller.delegate = self //----Important---//
//This is just a regular tableViewController nothing special
controller.modalPresentationStyle = .popover
// configure the Popover presentation controller
let popController: UIPopoverPresentationController? =
controller.popoverPresentationController
popController?.permittedArrowDirections = [.down]
popController?.delegate = self
popController?.sourceView = sender
popController?.sourceRect = sender.bounds
popController?.backgroundColor = .white
self.parent?.present(controller, animated: true, completion: { })
}
func itemSelected(item:String) {
//DISMISS YOUR POPOVER MAYBE AND DO SOMETHING WITH "ITEM" HERE
}
}
class TableViewController,UITableViewDelegate {
weak var delegate:PopoverOptionSelectionDelegate?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.itemSelected(self.itemsArray[indexPath.row])
}
}
Where and how do I have to reset hidesBarsOnSwipe? I set the option in a View Controller which I push and want to reset it for the View Controller which did the push. What I tried until now is setting hidesBarsOnSwipe to false in the viewDidDisappear and in the viewDidLoad of the pushing ViewController.
The Navigationbar is still disappearing.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (!visiblePOIs.isEmpty) {
let beaconInfo = visiblePOIs[indexPath.item]
var controller = storyboard!.instantiateViewControllerWithIdentifier("DetailController")! as! DetailController
controller.setup(beaconInfo)
self.parentViewController!.navigationController?.pushViewController(controller, animated: true)
}
}
DetailController
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
navigationController?.hidesBarsOnSwipe = true
self.automaticallyAdjustsScrollViewInsets = false
}
next view controller write in viewDidload
First Vc
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.hidesBarsOnSwipe = true
}
Second VC
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.hidesBarsOnSwipe = false
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
I hope its works
I had your exact problem. Here's how I solved it. (You can adapt this based on your needs.)
class MyViewController: UITableViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//Correct the nav bar state unwinding from segues
self.navigationController?.hidesBarsOnSwipe = true
}
override func willMoveToParentViewController(parent: UIViewController?) {
super.willMoveToParentViewController(parent)
//Toggle the auto-hiding nav bar when this view gets added/removed from the nav controller
self.navigationController?.hidesBarsOnSwipe = !self.navigationController!.hidesBarsOnSwipe
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
//Reset the nav bar to visible when segueing to another view
self.navigationController?.navigationBarHidden = false
self.navigationController?.hidesBarsOnSwipe = false
}
}
This approach allows you to limit the functionality of the auto-hiding feature to the desired view controller without adding code to all associated view controllers.