in my project I want to press on a button which is an address and after doing so it should swap to my mapVC and center the map on the address clicked.
One of the functions called when the button is pressed is the following:
func adressfunc(){
let mapvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LocationVC") as! LocationVC
CLGeocoder().geocodeAddressString(self.adresse, completionHandler: { (placemarks, error) -> Void in
if let placemark = placemarks?[0] {
let location = placemark.location!
print("location", location)
mapvc.centerMapOnPin(selectedPin: location)
}})
}
inside the mapVC I have the function centerMapOnPin :
func centerMapOnPin (selectedPin: CLLocation){
let newregion = MKCoordinateRegionMakeWithDistance(selectedPin.coordinate, regionRadius, regionRadius)
print(selectedPin, "pinsel")
print( newregion)
map.setRegion(newregion, animated: true)
}
Printing the location in both functions proved me that the location data got passed correctly but somehow I get the error at
map.setRegion(newregion, animated: true)
that says that it unexpectedly found nil while unwrapping an optional.
I can't understand which of them would be nil.
Try print(map) in the function centerMapOnPin. Maybe nil is printed.
let mapvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LocationVC") as! LocationVC
At this time map has not been loaded yet.
Related
I am programming on swift language and I want to reuse this code passing the identifier as String and the page as the view controller name, but I am getting the error Use of undeclared type 'page', how can I achieve this? thanks
func toReuseSession(identifier: String, **page**: UIViewController){
let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let mainVC = mainStoryBoard.instantiateViewController(withIdentifier: identifier) as? **page** else {
return
}
mainVC.modalPresentationStyle = .fullScreen
present(mainVC, animated: true, completion: nil)
}
According to your function, You should be declared a generic type of UIViewController in such a way you can achieve your output. I have modified your function in a correct syntax, You can use that:-
func toReuseSession<T:UIViewController>(identifier: String, page: T){
let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let mainVC = mainStoryBoard.instantiateViewController(withIdentifier: identifier) as? T else {
return
}
mainVC.modalPresentationStyle = .fullScreen
present(mainVC, animated: true, completion: nil)
}
Now you can call your function like that:-
self.toReuseSession(identifier: "NewPasswordViewController", page: NewPasswordViewController())
// "NewPasswordViewController" is in my case when I was checking it's working. You can change whatever ViewController wants to present.
This is a possible solution with generics but you have to pass the static type in the second parameter
extension UIViewController {
func toReuseSession<T>(identifier: String, page: T.Type) where T : UIViewController {
let mainStoryBoard = UIStoryboard(name: "Main", bundle: .main)
guard let mainVC = mainStoryBoard.instantiateViewController(withIdentifier: identifier) as? T else {
return
}
mainVC.modalPresentationStyle = .fullScreen
present(mainVC, animated: true, completion: nil)
}
}
I have a ViewController that I would like to show the activity indicator while fetching username data from firebase and changing the username on the MenuViewController. After that, it would show the MenuViewController. But the problem I'm facing is the "menuVC.greetText = "Hi " + username" line doesn't work. There is no username being shown on the ViewController. Any advice is appreciated. Thanks in advance.
class LoadingViewController: UIViewController {
#IBOutlet var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
startLoading {
switchToMenu()
}
// Do any additional setup after loading the view.
}
func startLoading(finished: () -> Void) {
activityIndicator.startAnimating()
authenticateUserAndConfigureView()
finished()
activityIndicator.stopAnimating()
}
func switchToMenu() {
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainViewController = storyboard.instantiateViewController(identifier: "MainVC")
self.view.window?.rootViewController = mainViewController
self.view.window?.makeKeyAndVisible()
}
}
func authenticateUserAndConfigureView() {
if Auth.auth().currentUser == nil {
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginController = storyboard.instantiateViewController(identifier: "LoginViewController")
self.view.window?.rootViewController = loginController
self.view.window?.makeKeyAndVisible()
}
}
else {
//let tabBar = segue.destination as! UITabBarController
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBar = storyboard.instantiateViewController(identifier: "MainVC") as! UITabBarController
let menuVC = tabBar.viewControllers?[0] as! MenuViewController
guard let uid = Auth.auth().currentUser?.uid else {return}
Database.database().reference().child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let username = value?["Username"] as? String ?? ""
menuVC.greetText = "Hi " + username
}
)
}
}
}
You are dealing with asynchronous function here and you are also inside closure, so meaning that as soon as you leave the closure, the assigned values inside closure will not be available. You need completionHandler to deal with it. You can make a separate function of it and then call it inside authenticateUserAndConfigureView.
func getName(completion:#escaping((String)->()) {
Database.database().reference().child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let username = value?["Username"] as? String ?? ""
completion(name)
}
And then you can call this function like this.
getName{(nameOfUser) in
print("Name is \(nameOfUser)")
}
In your case:
Calling inside authenticateUserAndConfigureView
func authenticateUserAndConfigureView() {
if (......) {
//All your code
}
else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBar = storyboard.instantiateViewController(identifier: "MainVC") as! UITabBarController
let menuVC = tabBar.viewControllers?[0] as! MenuViewController
self.getName{(name) in
menuVC.greetText = "Hi" + name
}
}
}
You may need to use DispatchQueue here for possible timing issue, but I am not sure about that. If you do, let me know.
I have a keyboard extension and want the user to be able to tap on "settings", and be taken to the settings page inside the actual app. I was pretty excited when I got that working, but I realized that the page wasn't inside the navigation controller anymore!
Here is my code. How can I augment it to present a specific sub-screen inside my 'TransparentNavigationController'?
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
let urlPath : String = url.path as String!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlPath == "/inner"){
let innerPage: myStampsVC = mainStoryboard.instantiateViewController(withIdentifier: "myStampsVC") as! myStampsVC
self.window?.rootViewController = innerPage
}
self.window?.makeKeyAndVisible()
return true
}
I've tried different mixes of
let navigationController = window?.rootViewController as! UINavigationController
let innerPage = navigationController.viewControllers[4] as! myStampsVC
but I'm missing something. Thanks for any nudges in the right direction.
Here's the answer:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
let urlPath = url.path
if(urlPath == "/inner"){
func showMyStamps(in navigationController: UINavigationController) {
navigationController.popToRootViewController(animated: false)
_ = navigationController.topViewController?.view
let innerViewController = myStampsVC() as UIViewController
navigationController.pushViewController(innerViewController, animated: false)
}
switch window!.rootViewController {
case let demo as DemoScreen:
if let navC = demo.presentedViewController as? UINavigationController {
showMyStamps(in: navC)
} else {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let navC = storyBoard.instantiateViewController(withIdentifier: "TransparentNavController") as! UINavigationController
demo.present(navC, animated: false)
showMyStamps(in: navC)
}
case let navC as UINavigationController:
showMyStamps(in: navC)
default: assertionFailure()
}
}
return true
}
func presentLoggedInScreen() {
let stroyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let logginedInVCViewController:LogginedInVCViewController = storyboard.instantiateViewController(withIdentifier: "LogginedInVCViewController" as! LogginedInVCViewController,
self.present(logginedInVCViewController, animated: true, completion: nil))
}
How can I avoid this error?
variable used within its own initial value
Try this:
func presentLoggedInScreen() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let logginedInVCViewController = storyboard.instantiateViewController(withIdentifier: "LogginedInVCViewController") as? LogginedInVCViewController {
self.present(logginedInVCViewController, animated: true, completion: nil)
}
}
EDIT:
Use optional binding and whats causing your error is the , and extra )
Sometimes the intellisense of XCode doesnt work properly, so try to analyze the problem first :)
I am developing a walkthrough screen which should appear only the first time when the user opens the app. So far I created the walkthrough page and PageViewController.
See the picture:
I read lots of similar questions here and I understood that I have to use
UserDefaults()
inside the AppDelegate, but I didn't understand how to use the class and storyboard names inside the code.
Basically, when the app is opened for the first time, the PageViewController should appear on the screen and when the user clicks on the start button, which is on the WalkThroughScreen it will dismiss the tutorial page and the app will start.
I have tried this code:
if let isFirstStart = UserDefaults.standard.value(forKey: "isFirstLaunch") as? Bool {
if defaults.bool(forKey: "isFirstLaunch") {
defaults.set(false, forKey: "isFirstLaunch")
let mainStoryboard = UIStoryboard(name: "WalkThroughScreen", bundle: Bundle.main)
let vc : WalkThroughScreen = mainStoryboard.instantiateViewController(withIdentifier: "PageViewController") as! WalkThroughScreen
self.present(vc, animated: true, completion: nil)
}
I am pretty sure that it is a complete mess, because I didn't understand it very good and I haven't used the TutorialPage, so I will be very thankful if someone leave me hints or example how to do it correctly
Yes you are right, you have to use userDefaults to achieve this. And you have to do it inside appDelegate()
Roel Koops answer should do it but you can try it like this also:
let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")
if launchedBefore {
print("This is not first launch.")
} else {
print("This is first launch.")
UserDefaults.standard.set(true, forKey: "launchedBefore")
UserDefaults.standard.synchronize()
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc : WalkThroughScreen = mainStoryboard.instantiateViewController(withIdentifier: "WalkThroughScreen") as! WalkThroughScreen
self.present(vc, animated: true, completion: nil)
}
And make sure to declare: let userDefaults = UserDefaults.standard and use it inside didFinishLaunchingWithOptions.
There are even more solutions so I give you one more:
let userDefaults = UserDefaults.standard
if !userDefaults.bool(forKey: "launchedBefore") {
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc : WelcomeViewController = mainStoryboard.instantiateViewController(withIdentifier: "WalkThroughScreen") as! WelcomeViewController
self.window?.makeKeyAndVisible()
self.window?.rootViewController?.present(vc, animated: false, completion: nil)
userDefaults.set(true, forKey: "launchedBefore")
userDefaults.synchronize()
}
You can even declare all the storyboard thing in one line:
self.window?.rootViewController = self.storyboard?.instantiateViewController(withIdentifier: "WalkThroughScreen")
But this assumes that you declare 2 variables:
var window: UIWindow?
var storyboard: UIStoryboard?
If it didn't work just tell me, I am gonna try to help.
The code in your if-block is never executed if the key "isFirstLaunch" doesn't exist.
Try this:
if let isFirstStart = UserDefaults.standard.value(forKey: "isFirstLaunch") as? Bool {
print("this is not the first launch")
} else {
print("this is the first launch")
UserDefaults.standard.set(false, forKey: "isFirstLaunch")
UserDefaults.standard.synchronize()
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc : WalkThroughScreen = mainStoryboard.instantiateViewController(withIdentifier: "WalkThroughScreen") as! WalkThroughScreen
self.present(vc, animated: true, completion: nil)
}