Swift popToViewController - swift

Good day guys, I'm learning Swift, needed some help here.
The user are signing up and selected their image. Upon dismissing the image picker, I would like to have the ComposeViewController appear.
Here is the code:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: NSDictionary!) {
let pickedImage:UIImage = info.objectForKey(UIImagePickerControllerOriginalImage) as UIImage
//Scale Down Image
let scaledImage = self.scaleImageWith(pickedImage, and: CGSizeMake(100,100))
let imageData = UIImagePNGRepresentation(scaledImage)
let imageFile:PFFile = PFFile(data: imageData)
PFUser.currentUser().setObject(imageFile, forKey: "profileImage")
PFUser.currentUser().saveInBackgroundWithTarget(nil, selector: nil)
picker.dismissViewControllerAnimated(true, completion: nil)
//this is the line seems to have problem.
self.navigationController?.popToViewController(ComposeViewController, animated: true)
}
Then I got these error:
ComposeViewController.Type' is not convertible to 'UIViewController
Expected member name or constructor call after type name
It has suggestion to fix by putting () after ComposeViewController but then it gives out more errors after fixing.
Hope someone could help. Thanks! :-)

let controllers = self.navigationController?.viewControllers
for vc in controllers! {
if vc is YourVC {
_ = self.navigationController?.popToViewController(vc as! YourVC, animated: true)
}
}

I know this is old, but it's like what Saqib said, you can't pop to a viewcontroller that doesn't exist yet.
A lot of the answers here seem to be from people that didn't read your question, just the title. I'll leave this code here in case it helps anyone.
let vcIndex = self.navigationController?.viewControllers.indexOf({ (viewController) -> Bool in
if let _ = viewController as? ComposeViewController {
return true
}
return false
})
let composeVC = self.navigationController?.viewControllers[vcIndex!] as! ComposeViewController
self.navigationController?.popToViewController(composeVC, animated: true)

There's a method that lets you get access to an array of all the ViewControllers on the current stack, and you can capture the one you want by using its index, for instance:
let switchViewController = self.navigationController?.viewControllers[1] as! ComposeViewController
self.navigationController?.popToViewController(switchViewController, animated: true)

if let composeViewController = self.navigationController?.viewControllers[1] {
self.navigationController?.popToViewController(composeViewController, animated: true)
}

I ended up replaceing the following code inside the main view and it works. I'm not sure if this is the right way, would you mind giving me some comments?
//self.navigationController?.popToViewController(ComposeViewController, animated: true)
let switchViewController = self.storyboard?.instantiateViewControllerWithIdentifier("view2") as ComposeViewController
self.navigationController?.pushViewController(switchViewController, animated: true)
I defined "view2" as the destination storyboard ID.

What I found more useful was to do a first lookup with viewControllers, that way you get the first instance you find in the stack, without having to guess the actual index.
e.g.
let mainViewControllerVC = self.navigationController?.viewControllers.first(where: { (viewcontroller) -> Bool in
return viewcontroller is ComposeViewController
})
if let mainViewControllerVC = mainViewControllerVC {
navigationController?.popToViewController(mainViewControllerVC, animated: true)
}

For Swift 4.0 and above Using Filter
guard let VC = self.navigationController?.viewControllers.filter({$0.isKind(of: YourViewController.self)}).first else {return}
self.navigationController?.popToViewController(VC, animated: true)

navigation controller maintains the stack of views you are pushing. Its like a Last in first out queue.
In order to pop to ComposeViewController, that view must already exist in the queue and you should have reference to it.
You will need to pass the instance of ComposeViewController. for simplicity you might save that reference in appdelegate. (this approach is not recommended)

for (var i = 0; i < self.navigationController?.viewControllers.count; i++)
{
if(self.navigationController?.viewControllers[i].isKindOfClass(DestinationViewController) == true)
{
self.navigationController?.popToViewController(self.navigationController!.viewControllers[i] as! DestinationViewController, animated: true)
break;
}
}

In Swift 4.1 and Xcode 9.4.1
Suppose if you moved from 1st ViewController to 2nd, then 2nd to 3rd. Now if you want to come back from 3rd to 1st directly this code is enough.
if let composeViewController = self.navigationController?.viewControllers[1] {//Here you mention your view controllers index, because navigation controller can store all VC'c in an array.
print(composeViewController)
self.navigationController?.popToViewController(composeViewController, animated: true)
}

Related

The screen comes out from behind

I don't know why this is happening, already I have a similar functions on the project and this isn't happening.
When I delete the account and I to the previous screen or if the user is on background and open it again the 2 methods below cause you to be sent to the main screen but I detected this problem (in the picture)
There is the function when I go to the home when I delete the account
private func goToHomeWithLogin(){
let home = HomeAssembly.presenterView()
Utils.getTopViewController()?.present(home, animated: true)
And the getTopViewController do this:
func getTopViewController() -> UIViewController? {
if let viewController = UIApplication.shared.keyWindow?.rootViewController {
if let modal = UIApplication.shared.keyWindow?.rootViewController?.presentedViewController {
return modal
} else if let navigationController = viewController as? UINavigationController {
return navigationController
}
}
return nil
}
When I drag down the screen comes out from behind
looks like default card style of UIViewController.present(...) if you want it full screen (without the swipe down feature) try home.modalPresentationStyle = .fullScreen before presenting it
let home = HomeAssembly.presenterView()
home.modalPresentationStyle = .fullScreen
Utils.getTopViewController()?.present(home, animated: true)

Is it possible to observe changes in presentingViewController?

Is there any equivalent in Swift to RACObserve(self, presentingViewController)?
Or any other why to imitate this behaviour?
My issue is that I want to be notified whenever a view controller is "hidden" by another view controller. In objc what I'd do is to check if self.presentingViewController is nil.
Note that in this scenario there's no knowledge of which view controller is presented, so it's impossible to notify from within its viewDidAppear/viewDidDisappear.
As I understand your question: you need to to know which view controller is presented now and you need notification inviewDidAppear/viewDidDisappear.
So we can get this in several way.
The simple way is:
Get information of which is the top ViewController right now.
2.Call this method in your viewDidAppear/viewDidDisappear
Like this :
Get Which is The Top ViewController
func getTopViewController() -> UIViewController? {
if var topVC = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topVC.presentedViewController {
topVC = presentedViewController
return topVC
}
return topVC
}
return nil
}
Call in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if let top = getTopViewController() {
print("topView Controller name \(top.title)")
top.view.backgroundColor = .red
}
}
Hope it will help you !

Run a function N time anywhere in the app in Swift

I trying to make a calling app for my project and I want to add a function that keeps checking if someone if calling. My app uses Firebase where I have a key for each users to check if he made a call or not.
There's two problem I am facing here, the first one is, as I said, that I want my function to keep checking anywhere in the app for an incoming call. The other problem is that i have a viewcontroller that I want to pop up when someone is calling. I have found this code on github but it uses navigationcontroller which I am not using in my app :
extension UIViewController{
func presentViewControllerFromVisibleViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self as? UINavigationController, let topViewController = navigationController.topViewController {
topViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else if (presentedViewController != nil) {
presentedViewController!.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else {
present(viewControllerToPresent, animated: true, completion: completion)
}
}
}
For your question on monitoring when incoming calls occur and to be called as a result, see this answer. It's probably what you need (I've never tried it, however). The example shows creating a CXCallObserver and setting your AppDelegate as delegate.
For your second question, I'd first try this answer which leverages the window.rootViewController so you can do this from your AppDelegate. Generally, the root VC is your friend when trying to do UI your AppDelegate. :)
A better answer based on Alex's added comments:
I'd first look at how to set up an observer to your Firebase model so that you can get a callback. If you don't have a way to do that, I'd use KVO on the Firebase model property. But to do exactly as you're requesting, and to do so lazily from AppDelegate (rather than from a singleton), see this code:
// In AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
{
self.timerToCheckForCalls = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
func timerFired()
{
let didCall = // TODO: query your Firebase model...
guard didCall == true else
{
return
}
self.displayCallerView()
}
func displayCallerView()
{
// See below link.
}
See this answer for how to present your view controller, even when your app might be showing an action sheet, alert, etc... which I think you'd especially value since you need to display the caller regardless of what your app is doing.
Note while user is scrolling a UITextView, the timer won't fire yet. There may be other situations where the timer could be delayed too. So it really would be best to observe your Firebase model or receive a KVO callback than to use a timer.
If you want to make a function that can be called from anywhere, use a singleton pattern. You can also use that to store your special view controller.
Bear in mind that this code SHOULD NOT considered fully functioning code and will need to be customized by you to suit your needs.
class MyClass {
let shared = MyClass()
var viewController: SpecialViewController?
func checkForCall() {
// do function stuff
}
func getSpecialViewController() {
let storyBoard = UIStoryboard.init(name: "main", bundle: nil)
// keep it so we don't have to instantiate it every time
if viewController == nil {
viewController = storyBoard.instantiateViewController(withIdentifier: "SomeViewController")
}
return viewController
}
}
// Make an extension for UIViewController so that they can all
// use this function
extension UIViewController {
func presentSpecialViewController() {
let vc = MyClass.shared.getSpecialViewController()
present(vc, animated: false, completion: nil)
}
}
Somewhere in your code:
// in some function
MyClass.shared.checkForCall()
Somewhere else in code:
presentSpecialViewController()

call uiactivityviewcontroller with spritekit/skscene

I'm trying to add a share button to social media from within my game to share a highscore. I can't seem to figure it out, and from various other answers, I've arrived at this code (which throws a NSInternalInconsistencyException). Any ideas?
let savedScore = UserDefaults.standard.value(forKey: "HighestScore") as! Int
let textToShare = "My highscore on Panda Pong is \(savedScore)! Can you beat that?"
let objectsToShare = [textToShare]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.excludedActivityTypes = [UIActivityType.airDrop, UIActivityType.addToReadingList]
let vc = UIViewController(nibName: "testview", bundle: nil) as UIViewController
vc.present(activityVC, animated:true, completion:nil)
The correct code is:
let vc = self.view!.window!.rootViewController!
vc.present(activityVC, animated:true, completion: nil)
Not exactly sure why, but messed around with some syntax and arrived at this answer.
You can do it this way in spritekit
...
let rootViewController = view?.window?.rootViewController
rootViewController?.present(activityVC, animated:true, completion:nil)
Instead of trying to instantiate a new ViewController just use the rootViewController (GameViewController).
I would also reccomend to not do things such as
let savedScore = UserDefaults.standard.value(forKey: "HighestScore") as! Int
with force casting the user defaults value (as! Int). You will crash if there is no value yet so change it to this
let savedScore = UserDefaults.standard.value(forKey: "HighestScore") as? Int ?? 0
You now safely check if the UserDefaults value exists and is an Int (as? Int) and if not it will create a new default value (?? 0).
Furthermore try to put your UserDefaults String keys ("HighestScore") into a constant property so you avoid typos.
enum UserDefaultsKey: String {
case highscore = "HighestScore"
}
and than use it like so
UserDefaults.standard.value(forKey: UserDefaultsKey.highscore.rawValue)
Hope this helps

tvOS: An issue while navigating

I am trying to navigate to another UIViewController and seems nothing works. My code is:
private func navigate() {
let isAutoLogin = getAutologinValue()
if isAutoLogin {
let mainTabsController = self.storyboard?.instantiateViewControllerWithIdentifier("Tabbar") as? LTTabbar
self.navigationController?.pushViewController(mainTabsController!, animated: true)
}
else {
let subscribeController = self.storyboard?.instantiateViewControllerWithIdentifier("SplashScreen") as? LTSplashCsreenController
self.navigationController?.pushViewController(subscribeController!, animated: true)
}
}
I call navigate() in viewDidAppear. Should it work like this in tvOS? Thank you in advance.
You should call the instantiateViewControllerWithIdentifier method with a LTTabBar.
What is wrong is to call this method with an instance of UIViewController or UITableViewController... As you are doing in the second method.