So I'm building an app for the first time and I am using https://github.com/watsonbox/ios_google_places_autocomplete. My code is down below.
func testFunction() {
println(gpaViewController)
let view2 = self.storyboard?.instantiateViewControllerWithIdentifier("view2") as! ViewController2
self.showDetailViewController(view2, sender: self)
}
And in the extension ViewController I call the testFunction:
extension ViewController: GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place) {
println(place.description)
place.getDetails { details in
println(details)
}
self.dismissViewControllerAnimated(true, completion: nil)
println(self)
testFunction()
}
func placeViewClosed() {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Basically, I want to switch views after I click on the tableView cell. However, nothing happens. But when I attach the testFunction to IBaction and a button, it takes me to another viewController.
Related
I have two Present view controllers. The thing i want to do is when the second Present view controller is dismissed it will automatically reload the first present view controller(Table view). note: first view controller holds a table view, basically i want to reload the table view of first controller.
ViewWillAppear code:
override func viewWillAppear(_ animated: Bool) {
tableViewReloadFromCreateProductVC()
}
func tableViewReloadFromCreateProductVC () {
tableView.reloadData()
}
Calling from second view controller code:
SecondViewController.tableViewReloadFromCreateProductVC()
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
FirstViewController calling 2nd view controller
#IBAction func CallSecondViewButton(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourViewControllerIdentifier") as! YourViewController
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
just write the code in viewWillAppear() method of the view controller that you want to reload like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
//perform api call if any
yourTableView.reloadData()
}
2nd view controller
#IBAction func CloseButton(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
after dissmissing the viewWillAppear method of firstViewController will autometically called.
The First two snippets are for first view controller and the last one is for second view controller
Reloading the entire table view could sometimes be costly and it also sounds like you're making an API call as well so unless you want your table view to be reloaded and the API call made every time the view controller becomes visible whether or not you've made changes to it, you want the reloading to be done only when it's necessary.
You can try it in a few different ways:
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
weak var delegate: CreateProductVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.delegate?.tableView.reloadData()
}
}
}
or
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.isDismissed = { [weak self] in
self?.tableView.reloadData()
}
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
var isDismissed: (() -> Void)?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.isDismissed?()
}
}
}
or if you want more fine-grained control over what to do with the new data:
protocol ReloadVC {
func reload(_ value: String)
}
class CreateProductVC: UITableViewController, ReloadVC {
var dataSource: [String]! {
didSet {
tableView.reloadData()
}
}
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
func reload(_ value: String) {
dataSource.append(value)
}
}
class SecondViewController: UIViewController {
var delegate: ReloadVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
let someValue = "Some Value"
self.delegate?.reload(someValue)
}
}
}
I have set a "Show as popover" segue between a UIView (A) and another UIView (B) (embed in a Navigation Controller) activated on a button's clic.
i am trying to pass datas back from (B) to (A) when i dismiss it (i want to keep the popover animation on both ways).
I have tried many methods i found mostly here, on Stackoverflow, but as of now i never successfully retrieved my data on (A).
I tried Delegates and protocols as well as other simpler methods. The last in date is the following one:
In (A), i just try to print the variable that should be storing the datas in ViewWillAppear :
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var testValue:String = ""
override func viewWillAppear(_ animated: Bool) {
print(testValue) // print is empty
super.viewWillAppear(animated)
}
}
In (B), i dismiss the popover and try to send the datas back on a button clic :
class SearchFilterViewController: UIViewController {
#IBAction func DismissPopoverOnClic(_ sender: Any) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! SearchBarsController
presenter.testValue = "Test"
print("success") //never called
}
self.dismiss(animated: true, completion: nil)
}
}
on (B) i'd like to set up some filter that i'd use on (A) to present search results in a tableview. But actually the testValue's value is always blank.
oky so you can do it using unwind segue here is sample project :
sample projecct
process :
Add this method to SearchBarsController below viewWillAppear
#IBAction func unWindFromFilterViewController(_ sender: UIStoryboardSegue) {
}
Than go to Storyboard and go to SearchFilterViewController and then cntrl + Drag from DismissPopoverOnClic to top of the exit button then select unWindFromFilterViewController .
Than this the SearchFilterViewController write this method for passing data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? ViewController {
destVC.testValue = "Test"
}
}
You will get your desired data back . thanks
When passing back data to a viewController, the most efficient way to implement it using delegate
protocol SearchFilterViewControllerDelegate {
func setTextValue(string : String)
}
class SearchFilterViewController: UIViewController {
var delegate : SearchFilterViewControllerDelegate?
#IBAction func DismissPopoverOnClic(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
delegate?.setTextValue(string : "Test Value")
}
}
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var filterViewController : SearchFilterViewController?
func popup() {
// your pop up code and init filterViewController
filterViewController.delegate = self **//without this line, the delegate will be nil, no nothing will happen.**
}
}
extension SearchBarsController : SearchFilterViewControllerDelegate {
func setTextValue(string : String) {
print(string)
}
}
So I have two view controllers in my application:
StartViewController (Root view controller) and GameViewController
I am presenting my Rewarded video from GameViewController
Everything works perfectly, except for when the user presses "Skip now" on the rewarded video. It dismisses GameViewController and goes back to StartViewController which is my root view controller.
If the user watches the entire video, it works as intended.
The code for presenting rewarded view from GameViewController:
func playReward()
{
if rewardVideo!.isReady
{
if var topController = UIApplication.shared.keyWindow?.rootViewController
{
while let presentedViewController = topController.presentedViewController
{
// Make top controller topmost view controller
topController = presentedViewController
}
rewardVideo!.present(fromRootViewController: topController)
}
}
}
I temporarily changed the root to GameViewController to see if this was the issue and doing so fixed it, so I know it is an issue related to the root view controller and the "Skip now" button on the rewarded video.
I had the same issue. I solved it by overriding the func dismiss(animated flag: Bool, completion: (() -> Void)? = nil method.
Here is what I did.
var didOpenRewardedVideo:Int = 0
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if didOpenRewardedVideo == 1 {
didOpenRewardedVideo = 2
super.dismiss(animated: flag, completion: completion)
}
else if didOpenRewardedVideo == 2{
didOpenRewardedVideo = 0
}
else{
super.dismiss(animated: flag, completion: completion)
}
}
func showRewardedVideo()
{
didOpenRewardedVideo = 1
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self)
}
Before showing the rewardedAd, do not forget to check if it's ready or not.
GADRewardBasedVideoAd.sharedInstance().isReady == true
When rewardedAd is presented didOpenRewardedVideo is set to 1. When user dismissing rewardedAd didOpenRewardedVideo is 1 and calling super.dismiss(animated: flag, completion: completion). Then didOpenRewardedVideo is set to 2. Now I know dismiss(animated flag: Bool, completion: (() -> Void)? = nil) will be called once more. This time I don't call the super method and set didOpenRewardedVideo to 0. I know if I dismiss my UIViewController will be dismissed.
#mialkan,
your solution does not work. I don't why no one talk about this issue. I am also facing such problem.
import GoogleMobileAds import sdk in your class or viewcontroller
GADRewardBasedVideoAdDelegate Add this in your class or viewcontroller
var RewardBasedVideo: GADRewardBasedVideoAd? **initialize AdController object **
override func viewDidLoad()
{
super.viewDidLoad()
RewardBasedVideo=GADRewardBasedVideoAd.sharedInstance()
RewardBasedVideo?.delegate = self
}
//MARK:- WATCH AD BUTTON CLICK
#IBAction func WatchAdBtn_Click(_ sender: UIButton)
{
if RewardBasedVideo?.isReady == true
{
RewardBasedVideo?.present(fromRootViewController: self)
} else
{
//Show alert here "Ads is not ready to load"
}
}
func rewardBasedVideoAdDidClose(_ rewardBasedVideoAd: GADRewardBasedVideoAd)
{
print("Reward based video ad is closed.")
}
For those of you who is facing the the same issue:
You can create a new class for your rootViewController (TabBarController or NavigationController etc.) and implement there something like that:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
dismissalCounter += 1
if (dismissalCounter < 2) {
super.dismiss(animated: flag, completion: completion)
}
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
dismissalCounter = 0
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
dismissalCounter = 0
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
var dismissalCounter : Int = 0
Important! Use this functions inside TabBarController or NavigationController, otherwise it is not gonna work
UPD:
In my case unfortunatly it breaks all NavigationControllers inside a TabBarController (titles don't show and there are no buttons inside them), if I will figure fix actions, I'll let you know
UPD2:
Pretty obvious decision will be to change the initialViewController and view add from it, it'll not be dismissed
UPD3:
I solved this very and very strange:
class ViewController : UIViewController {
override func viewWillAppear(_ animated: Bool) {
if GADRewardBasedVideoAd.sharedInstance().isReady == false {
let request = GADRequest()
rewardBasedVideo!.load(request, withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
}
}
var rewardBasedVideo: GADRewardBasedVideoAd?
#IBAction func ad_button_click(_ sender: Any) {
if rewardBasedVideo!.isReady == true {
let bl = blur()
self.present(bl, animated: true, completion: {
self.rewardBasedVideo?.present(fromRootViewController: bl)
})
}
}
}
class blur : UIViewController {
override func viewDidLoad() {
checkForKeyWindow()
}
func checkForKeyWindow() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
if (UIApplication.topViewController() == self) {
print("dismissed and forgotten")
self.dismiss(animated: true, completion: nil)
} else {
print("not keywindow")
self.checkForKeyWindow()
}
})
}
#objc func close() {
self.dismiss(animated: true, completion: nil)
}
}
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?
You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC
While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.
Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}
In my app I have a SFSafariViewController that I am displaying modally. Upon dismissal, the presenting ViewController does not have its dismiss method called. Code for my subclass of UIViewController:
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
print("will present")
super.present(viewControllerToPresent, animated: flag) {
completion?()
print("did present")
}
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
print("will dismiss)")
super.dismiss(animated: flag) {
completion?()
print("did dismiss")
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
print("finished with \(controller)")
}
func testSVC() {
let svc = SFSafariViewController(url: URL(string: "https://www.stackoverflow.com")!)
svc.delegate = self
self.present(svc, animated: true) {
print("presented \(svc)")
}
}
Calling testSVC() and then tapping "Done" in the SafariViewController produces the following output:
will present
presented <SFSafariViewController: 0x7fd981b2f200>
did present
finished with <SFSafariViewController: 0x7fd981b2f200>
And that's it. The dismiss print statements are missing. Can anyone help me figure out why dismiss is not being called? I thought that all UIViewController dismissals were forwarded to the presenting UIViewController.
When you dismiss the SFSafariViewController the dismiss method for svc is fired not the ViewController you're in. If you want to override the dismiss for the SFSafariViewController then you should subclass it and override the dismiss func in that subclass.