This question already has answers here:
Detecting sheet was dismissed on iOS 13
(13 answers)
Closed 1 year ago.
In StoreViewController, when a button is clicked, a ModalViewController, named AddStoreVC is opened.
Then at AddStoreVC, when I press Save, I want to dismiss itself (which I can) and when StoreViewController is loaded, the tableView to be refreshed.
viewWillAppear, viewDidAppear or none of the alternatives on previous threads are applicable.
I'd appreciate for your support.
You can use a closure, delegate or notifications (and even KVO would also be a solution). Since you have a relation of one-one, I'd go with closure or pattern.
Closure:
Add in the ViewController which will be modally presented (AddStoreVC)
var onWillDismiss: (() -> Void)?
When you call dismiss(animated:completion:) on it, call onWillDismiss?()
In the presenting ViewController, get a reference on the modal one, and do:
modalVC.onWillDismiss = { [weak self] in
self?.myTableView.reloadData()
}
I passed no param (()), but if you also want to retrieve a param, add it. Imagine you want an Int:
var onWillDismiss: ((Int) -> Void)?
onWillDismiss?(theIntIWantToPass)
modalVC.onWillDismiss = { [weak self] theIntPassed in
print(theIntPassed)
self?.myTableView.reloadData()
}
Delegate :
You can also use the delegate pattern:
Create the delegate:
protocol AddStoreVCCustomProtocol {
func modalVCWillDismiss(_ modalVC: AddStoreVC)
func modalVC(_ modalVC, willDimissWithParam param: Int)
}
Make the presenting one compliant to it:
extension StoreViewController: AddStoreVCCustomProtocol {
func modalVCWillDismiss(_ modalVC: AddStoreVC) {
myTableView.reloadData()
}
func modalVC(_ modalVC, willDimissWithParam param: Int) {
print("theIntPassed with delegate: \(param)")
myTableView.reloadData()
}
}
Add a property to the Modal to have a delegate:
weak var customDelegate: AddStoreVCCustomProtocol?
And call it on dismiss(animated:completion:): customDelegate?.modalVCWillDismiss(self) or `customDelegate?.modalVC(self, willDimissWithParam: theIntIWantToPass)
Related
This question already has answers here:
Detecting sheet was dismissed on iOS 13
(13 answers)
Closed 8 months ago.
Is there a way to place a completion handler in the parent UIViewController that gets called after its child UIViewController dismisses itself?
For my current project a UIViewController might have to create and present a child UIViewController to get missing data.
My thought was that the completion handler in the present method wouldn't be called till the child UIViewController dismisses itself.
Obviously I was wrong, the completion handler is called immediately after the child UIViewController is presented, yet still exists.
This is an extremely simplified code, just so I can see when the completion handler is being called within the debugger.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
...determine if data is missing...
if *data is missing* {
let myUp = UploadInv()
myUpload.modalPresentationStyle = .fullScreen
myUpload.modalTransitionStyle = .flipHorizontal
x += 1
self.present(myUpload, animated: true, completion: { [self] in print ("\(x)");})
}}
That completion block is called when your UploadInv was presented. If you want to handle dismissing action from child controller you have to define it inside UploadInv
class UploadInv: UIViewController {
....
var onDismiss: (() -> ())?
func onDismissAction() {
self.dismiss(animated: true) {
onDismiss?()
}
}
}
Then use it whenever create an UploadInv instance if needed
let myUp = UploadInv()
myUp.onDismiss = { [weak self] in
//TODO: - Do something
}
I am building an iOS app using the new language Swift. Now it is an HTML5 app, that displays HTML content using the UIWebView. The app has local notifications, and what i want to do is trigger a specific javascript method in the UIWebView when the app enters foreground by clicking (touching) the local notification.
I have had a look at this question, but it does not seem to solve my problem. I have also come across this question which tells me about using UIApplicationState, which is good as that would help me know the the app enters foreground from a notification. But when the app resumes and how do i invoke a method in the viewController of the view that gets displayed when the app resumes?
What i would like to do is get an instance of my ViewController and set a property in it to true. Something as follows
class FirstViewController: UIViewController,UIWebViewDelegate {
var execute:Bool = false;
#IBOutlet var tasksView: UIWebView!
}
And in my AppDelegate i have the method
func applicationWillEnterForeground(application: UIApplication!) {
let viewController = self.window!.rootViewController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("FirstView") as FirstViewController
setViewController.execute = true;
}
so what i would like to do is when the app enters foreground again, i want to look at the execute variable and run the method as follows,
if execute{
tasksView.stringByEvaluatingJavaScriptFromString("document.getElementById('sample').click()");
}
Where should i put the code for the logic to trigger the javascript from the webview? would it be on viewDidLoad method, or one of the webView delegate methods? i have tried to put that code in the viewDidLoad method but the value of the boolean execute is set to its initial value and not the value set in the delegate when the app enters foreground.
If I want a view controller to be notified when the app is brought back to the foreground, I might just register for the UIApplication.willEnterForegroundNotification notification (bypassing the app delegate method entirely):
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Note, in the completion closure, I include [unowned self] to avoid strong reference cycle that prevents the view controller from being deallocated if you happen to reference self inside the block (which you presumably will need to do if you're going to be updating a class variable or do practically anything interesting).
Also note that I remove the observer even though a casual reading of the removeObserver documentation might lead one to conclude is unnecessary:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
But, when using this block-based rendition, you really do need to remove the notification center observer. As the documentation for addObserver(forName:object:queue:using:) says:
To unregister observations, you pass the object returned by this method to removeObserver(_:). You must invoke removeObserver(_:) or removeObserver(_:name:object:) before any object specified by addObserver(forName:object:queue:using:) is deallocated.
I like to use the Publisher initializer of NotificationCenter. Using that you can subscribe to any NSNotification using Combine.
import UIKit
import Combine
class MyFunkyViewController: UIViewController {
/// The cancel bag containing all the subscriptions.
private var cancelBag: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
addSubscribers()
}
/// Adds all the subscribers.
private func addSubscribers() {
NotificationCenter
.Publisher(center: .default,
name: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.doSomething()
}
.store(in: &cancelBag)
}
/// Called when entering foreground.
private func doSomething() {
print("Hello foreground!")
}
}
Add Below Code in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector:#selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForeground() {
print("App moved to foreground!")
}
In Swift 3, it replaces and generates the following.
override func viewDidLoad() {
super.viewDidLoad()
foregroundNotification = NotificationCenter.default.addObserver(forName:
NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: OperationQueue.main) {
[unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
I am using sheet presentation to view settings view iOS13.when the user dismisses or closes the view I want to trigger reload to previews viewController to update the view.
I tried viewWillAppear in previews ViewController but No user
note even viewDidLoad
how to force previews viewController in sheet presentation?
Override the dismiss function in your settings View Controller and write a delegate to send the reload action.
Assign the delegate to the view controller that you want to send the reload information.
protocol MyViewControllerDelegate: class {
func myViewControllerDidDismiss()
}
class MyViewController: UIViewController {
weak var delegate: MyViewControllerDelegate?
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
self.delegate?.myViewControllerDidDismiss()
super.dismiss(animated: flag, completion: completion)
}
}
Other way is making the protocol and overriding viewDidDisappear and send the delegate method when the settings view controller execute the viewDidDisappear. Feel free!
Passing data to the next viewController is simple and straightforward, and can be done using prepareSegue method. However, I can't understand how to pass data to a previous viewController in Swift(Cocoa Application)
I have a textfield in viewControllerB and when you type something in it and press a button, I want to pass it to a label in viewControllerA and instead of opening the viewControllerA in a new window, I just want the viewController B to be dismissed and the passed data to be visible on the viewControllerA.
That's all there is to it. I have been stuck on it for the past 48 hours. Any help on this will be appreciated.
Thanks!
You do this using delegates. Example:
protocol NextProtocol: class {
func sendBack(value: Int)
}
class Previous: NextProtocol {
func sendBack(value: Int) {
print("I have received \(value)")
}
func prepareSegue(...) {
// get next instance
var next: Next
next.delegate = self
}
}
class Next {
weak var delegate: NextProtocol?
func someMethod() {
delegate?.sendBack(5)
}
}
I'm trying to enable or disable an #IBOutlet UIButton Item of a toolbar from a UIView.
The button should get disabled when the array that I'm using in EraseView.Swift is empty.
I tried creating an instance of the view controller but it gives me the error (found nil while unwrapping):
in EraseView:
class EraseView: UIView {
...
let editViewController = EditImageViewController()
//array has item
editViewController.undoEraseButton.enabled = true //here I get the error
...
}
I tried to put a global Bool that changed the value using it in EditImageViewController but it doesn't work:
var enableUndoButton = false
class EditImageViewController: UIViewController {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
viewDidLoad() {
undoEraseButton.enabled = enableUndoButton
}
}
class EraseView: UIView {
...
//array has item
enableUndoButton = true //here I get the error
...
}
I know it's simple but I can't let it work. Here's the situation:
The root of the problem is the line that says:
let editViewController = EditImageViewController()
The EditImageViewController() says "ignore what the storyboard has already instantiated for me, but rather instantiate another view controller with no outlets hooked up and use that." Clearly, that's not what you want.
You need to provide some way for the EraseView to inform the existing view controller whether there was some change to its "is empty" state. And, ideally, you want to do this in a way that keeps these two classes loosely coupled. The EraseView should only be informing the view controller of the change of the "is empty" state, and the view controller should initiate the updating of the other subviews (i.e. the button). A view really shouldn't be updating another view's outlets.
There are two ways you might do that:
Closure:
You can give the EraseView a optional closure that it will call when it toggles from "empty" and "not empty":
var emptyStateChanged: ((Bool) -> ())?
Then it can call this when the state changes. E.g., when you delete the last item in the view, the EraseView can call that closure:
emptyStateChanged?(true)
Finally, for that to actually do anything, the view controller should supply the actual closure to enable and disable the button upon the state change:
override func viewDidLoad() {
super.viewDidLoad()
eraseView.emptyStateChanged = { [unowned self] isEmpty in
self.undoEraseButton.enabled = !isEmpty
}
}
Note, I used unowned to avoid strong reference cycle.
Delegate-protocol pattern:
So you might define a protocol to do that:
protocol EraseViewDelegate : class {
func eraseViewIsEmpty(empty: Bool)
}
Then give the EraseView a delegate property:
weak var delegate: EraseViewDelegate?
Note, that's weak to avoid strong reference cycles. (And that's also why I defined the protocol to be a class protocol, so that I could make it weak here.)
The EraseView would then call this delegate when the the view's "is empty" status changes. For example, when it becomes empty, it would inform its delegate accordingly:
delegate?.eraseViewIsEmpty(true)
Then, again, for this all to work, the view controller should (a) declare that is conforms to the protocol; (b) specify itself as the delegate of the EraseView; and (c) implement the eraseViewIsEmpty method, e.g.:
class EditImageViewController: UIViewController, EraseViewDelegate {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
eraseView.delegate = self
}
func eraseViewIsEmpty(empty: Bool) {
undoEraseButton.enabled = !empty
}
}
Both of these patterns keep the two classes loosely coupled, but allow the EraseView to inform its view controller of some event. It also eliminates the need for any global.
There are other approaches that could solve this problem, too, (e.g. notifications, KVN, etc.) but hopefully this illustrates the basic idea. Views should inform their view controller of any key events, and the view controller should take care of the updating of the other views.