Showing user location when button on custom UITabBarController is pressed - ios

I have a custom UITabBar controller, where I have buttons to switch between the view controllers. Here is an Image:
http://i.stack.imgur.com/UInLI.png
What I want to do it that when the main button is pressed the view will change to the mapViewController, and when it pressed again (on the same mapViewController) it will show to user Location.
I am using mapBox as my Map API, I have a function in my mapViewController,
findUserLocation()
and this show the user location by using:
mapView.userTrackingMode = MGLUserTrackingMode.Follow
So here is My code:
class CustomTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.hidden = true
let tabBarView = CustomTabBarView()
tabBarView.viewdidload()
self.view.addSubview(tabBarView)
tabBarView.mapButton.addTarget(self, action: #selector(self.changeToMyMap), forControlEvents: UIControlEvents.TouchUpInside)
// Do any additional setup after loading the view.
}
func changeToMyMap(){
if (self.selectedIndex != 0){
self.selectedIndex = 0
}else{
let mapViewController = myMap() // the class Name
mapViewController.mapView.userTrackingMode = MGLUserTrackingMode.Follow
}
}
}
So when I pressed the button to show the user location I get an error. I think is because the Map is not loaded into the customTabViewController, but I have no Idea how to make it work.
when I try to call a function
from my customTabBarController, my application get a fatal error.
fatal error: unexpectedly found nil while unwrapping an Optional value

If you haven't used them already, these may be some helpful mapBox resources.
I'm not sure why it would be crashing with what you have there.
I would try putting some breakpoints in to find out where it's actually crashing.
Try putting one on the line if (self.selectedIndex != 0){
to see if it's actually going inside that method.
If it is, try typing po yourPropertyName i.e. po self.selectedIndex in the debug console to try and find out what property is returning nil.
Also, if you know what properties are optional, check where you're accessing them. That's where they could be nil.

In our Swift application, we made use of showsUserLocation to toggle the user location.
This would be worth trying, if you are simply trying to show the User Location.
The MGLUserTrackingMode in Mapbox iOS SDK 3.2.3 is for how to track the user location on the map (e.g., the experience when following a driving route).
How you could use showsUserLocation
func changeToMyMap(){
// not sure if the 'if/else' logic fit with what you intended, but
// left your code intact to better highlight how to use mapView.showsUserLocation
if (self.selectedIndex != 0){
self.selectedIndex = 0
mapViewController.mapView.showsUserLocation = false
}else{
let mapViewController = myMap()
// causes the map view to use the Core Location framework to find the current location
mapViewController.mapView.showsUserLocation = true
}
}
}
Be aware of this note in the docs for Mapbox iOS SDK 3.2.3. You will need to pay attention to set new keys in the .plist.
On iOS 8 and above, your app must specify a value for
NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in
its Info.plist to satisfy the requirements of the underlying Core Location
framework when enabling this property.
For comparison of how we use showsUserLocation in Mapbox (what produces the experience in the above GIF) —
#IBAction func handleSettings(sender: AnyObject) {
var trackingLocation = UIImage(named: "TrackingLocationOffMask.png")
switch (self.mapView.userLocationVisible) {
case true:
self.mapView.showsUserLocation = false
trackingLocation = UIImage(named: "TrackingLocationOffMask.png")
break
case false:
self.mapView.showsUserLocation = true
trackingLocation = UIImage(named: "TrackingLocationMask.png")
break
}
self.navigationItem.leftBarButtonItem?.image = trackingLocation
}

Related

How to write XCUI test for whether I navigate to correct screen or not?

I am writing test UI test case for following UI
I want to test on Login click whether I am navigating correctly on Dashboard screen or not.
Is there any method to do this?
My current testing code is like
func testExample() {
let usernameTextField = app.textFields["Username"]
usernameTextField.tap()
usernameTextField.typeText("abc#gmail.com")
let passwordTextField = app.textFields["Password"]
passwordTextField.tap()
passwordTextField.typeText("abc123")
app.buttons["Login" ].tap()
//let loginButton = app.staticTexts["Login"]
//XCTAssertEqual(loginButton.exists, true)
app.navigationBars["UIView"].buttons["Back"].tap()
}
UI Tests can become really fragile when depending on text values. What I encourage you to do is to set the Accessibility Identifier for your ViewController's view. That way, even if you change the title or change the whole layout, you can still be sure you're in the correct Page/Screen/View.
class DashVC: UIViewController {
override func viewDidLoad() {
view.accessibilityIdentifier = "view_dashboard"
}
}
func test_login_withValidInput_goesDashBoard() {
let app = XCUIApplication()
//...
app.buttons["Login" ].tap()
let dashBoardView = app.otherElements["view_dashboard"]
let dashBoardShown = dashBoardView.waitForExistence(timeout: 5)
XCTAssert(dashBoardShown)
}
Try this
app.buttons["Login - Login"].tap()
XCTAssertEqual(app.navigationBars.element.identifier, "appname.CalculationView") //If your second view controller is SecondViewController, your identifier is appname.SecondView.Like that my second view controller is CalculationViewController so my identifier is CalculationView
Try adding an accessibility indicator to the back button so you can check for availability using backButton.exists or backButton.hittable and assert accordingly. In any case, if you set
continueAfterFailure = false
in setUp(), your test will fail as it is if it doesn’t find a button with “Back”.

Change View with NavigationViewController

all this is probably a trivial question, but I have not found a solution to it. I am making an app for Iphone using Swift.
I have a tableview with some strings and if I press a button I want to navigate back to the previous view directly. However, the code after my call
navigationController?.popViewControllerAnimated(true)
is always run, but I want the current activity to stop and go back to the previous view.
The code looks like:
#IBAction func DeletePressed(sender: UIButton) {
let deleteIndices = getIndexToDelete()
navigationController?.popViewControllerAnimated(true)
print("After navigationController")
for index in deleteIndices{
results?.ListResults[yearShownIndex].months[monthShownIndex].day[dayShownIndex].results.removeAtIndex(index)
}
if (results?.ListResults[yearShownIndex].months[monthShownIndex].day[dayShownIndex].results.count == 0){
results?.ListResults[yearShownIndex].months[monthShownIndex].day.removeAtIndex(dayShownIndex)
}
if (results?.ListResults[yearShownIndex].months[monthShownIndex].day.count == 0){
results?.ListResults[yearShownIndex].months.removeAtIndex(monthShownIndex)
}
if (results?.ListResults[yearShownIndex].months.count == 0){
results?.ListResults.removeAtIndex(monthShownIndex)
}
loadView()
}
"After navigationController" is always displayed.
In android you would start a new activity by creating intents to get the desired behaviour, but how does it work on Iphone?
My problem is that I want to be able to go back directly when navigationController.popViewControllerAnimated is called. This is just a toy example to understand how it works so that I can use it in the if-clauses later.
you could simply add a return statement after you pop the viewcontroller:
#IBAction func DeletePressed(sender: UIButton) {
let deleteIndices = getIndexToDelete()
navigationController?.popViewControllerAnimated(true)
return;
[...]
if you don't wants to execute code after "print("After navigationController")" then remove that code
or it is not possible to remove then toggle it when DeletePressed called

How to set accessibility only to annotations in map view?

I am trying to make my mapview accessible, but I have an isuue in doing so:
If I try to make the mapView accessible,by doing this:
self.mapView.isAccessibilityElement=YES;
Then, the map view is not read by the voice over.
If I set like this:
self.mapView.isAccessibilityElement=NO;
Then the voice over is reading everything in the map, streets, buildings, my current location and my annotations.
I have given the accessibility label and hints to my annotations,but I havent provided any other value to the mapview.
I also tried by setting the accessibility elements for map view:
[self.mapView setAccessibilityElements:#[self.mapView.annotations,self.mapView.userLocation]];
But still no luck.
Is there anyway to make voice over read only the annotations and neglect remaining elements like streets,buildings?
I think you're having difficulty because an MKMapView is a UIAccessibilityContainer, and so isAccessibilityElement if false by default.
You should look into making custom voice over rotors that allow the user to navigate your app by only your annotations.
This is a good approach, because it gives the user a custom, app specific way to navigate your map, while also not taking anything away from MKMapView's built in accessibility.
There are hardly any examples online that go into detail about creating custom rotors, but I just successfully created one doing exactly what you need it to do. I followed the WWDC Session 202 (begins at 24:17).
Here's my code:
func configureCustomRotors() {
let favoritesRotor = UIAccessibilityCustomRotor(name: "Bridges") { predicate in
let forward = (predicate.searchDirection == .next)
// which element is currently highlighted
let currentAnnotationView = predicate.currentItem.targetElement as? MKPinAnnotationView
let currentAnnotation = (currentAnnotationView?.annotation as? BridgeAnnotation)
// easy reference to all possible annotations
let allAnnotations = self.mapView.annotations.filter { $0 is BridgeAnnotation }
// we'll start our index either 1 less or 1 more, so we enter at either 0 or last element
var currentIndex = forward ? -1 : allAnnotations.count
// set our index to currentAnnotation's index if we can find it in allAnnotations
if let currentAnnotation = currentAnnotation {
if let index = allAnnotations.index(where: { (annotation) -> Bool in
return (annotation.coordinate.latitude == currentAnnotation.coordinate.latitude) &&
(annotation.coordinate.longitude == currentAnnotation.coordinate.longitude)
}) {
currentIndex = index
}
}
// now that we have our currentIndex, here's a helper to give us the next element
// the user is requesting
let nextIndex = {(index:Int) -> Int in forward ? index + 1 : index - 1}
currentIndex = nextIndex(currentIndex)
while currentIndex >= 0 && currentIndex < allAnnotations.count {
let requestedAnnotation = allAnnotations[currentIndex]
// i can't stress how important it is to have animated set to false. save yourself the 10 hours i burnt, and just go with it. if you set it to true, the map starts moving to the annotation, but there's no guarantee the annotation has an associated view yet, because it could still be animating. in which case the line below this one will be nil, and you'll have a whole bunch of annotations that can't be navigated to
self.mapView.setCenter(requestedAnnotation.coordinate, animated: false)
if let annotationView = self.mapView.view(for: requestedAnnotation) {
return UIAccessibilityCustomRotorItemResult(targetElement: annotationView, targetRange: nil)
}
currentIndex = nextIndex(currentIndex)
}
return nil
}
self.accessibilityCustomRotors = [favoritesRotor]
}

Problems with storing UISwitch state

I have a setting in my app that I am having issues retaining the state of. Basically, the UISwitch is on by default, however when updating my app to a newer version, it seems to switch off. I figured out that if the user has opened the settings menu in version 1.0, then this isn't an issue, however if they have never changed a setting, or even opened the settings menu, then it's a problem. Therefore, it must be an issue somewhere in viewWillAppear() but I can't figure out what it is. The code I have for storing the switches state seems really overcomplicated.
This is the code I have in my settings menu:
#IBAction func dupOffOnSwitch(sender: AnyObject) {
if dupSwitch.on == true {
autoAdjust = true
println(autoAdjust)
} else {
autoAdjust = false
println(autoAdjust)
}
NSUserDefaults.standardUserDefaults().setBool(autoAdjust, forKey: "autoAdjustSettings")
}
override func viewWillAppear(animated: Bool) {
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
if autoAdjust == true {
dupSwitch.on = true
} else {
dupSwitch.on = false
}
}
if userReturnedAuto == false {
dupSwitch.on = true
themeSwitch.on = false
userReturnedAuto = true
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "userReturnedAuto")
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "autoAdjustSettings")
}
I am declaring the bool 'autoAdjust' in a different view controller as a global variable. In that same view controller in viewWillAppear() I have this code:
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
Can anyone suggest a better way of storing the switches state while also having it on by default when the app is first launched? Or a fix to my current solution.
NSUserDefaults.standardUserDefaults().boolForKey("key") defaults to false. Therefore, as in your case, if the user never sets the key, then it will be false and the switch will default to off.
Instead, rename the key and use it as the negative. So instead, call it autoAdjustSettingsOff which will default to false and the switch will default to on. Don't forget to switch around the true/false settings in your conditional blocks.
Not sure if this is over kill(or just stupid). But why not use CoreData to save all the users settings. Then when the app becomes active(in your appDelegate) you can set the NSUserDefaults from these stored values. Just a thought :)
For shortness I will only make an example for one switch, you can then fill out and add the rest. I will also assume you will be updating the NSUserDefaults when the switch changes state. We will only update/save the core data when the app goes to in-active and load settings once when app becomes active.
Also if anyone sees something I missed or could be done better please let me know and I will update the answer.
First off you need to make sure you import the CodeData libraries into your view controller:
import CoreData
class fooBarViewController: UIViewController {
You need to create and entity in core data: lets assume it is called "Settings"
then once you have created the entity, you need to add some attributes. Press the "+" symbol.
Now add the attribute and set the name to "dupSwitch" and type to "Boolean". Add an attribute for each setting you need to store.
Next: You need to create a new swift file. Type NSObject. name it something like "dataObjects".
Inside you you will use the following code: (first remove all current code - make it blank)
import UIKit
import CoreData
#objc(settingObj)
class settingObj: NSManagedObject {
#NSManaged var dupSwitch:NSNumber //do this for each setting
}
Next you need to go back to your CoreData dataModel and do the following.
1. click on "Default" under the section "Configuration"
2. You will then get a list of all your "entities" select "Settings"
3. click on class and edit the field to "settingObj"
Now that you have you CoreData set up. You can now save/edit/delete data as need be.
For example when you need to load all saved user settings when app goes will become active.
in your AppDelegate.swift file: import the CoreData libraries.
import CoreData
then add this function if it is not already present
func applicationWillEnterForeground(application: UIApplication) {
}
inside "applicationWillEnterForeground" you will load any settings that have been stored in CoreData
var results:[settingObj] = []
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "Settings")
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [settingObj] {
for item in castedResults {
let dupSwitch = item.dupSwitch
let settingSwitch = item.whateverElseYouNeedExample
//now based on this we can set the NSDefault
NSUserDefaults.standardUserDefaults().setBool(dupSwitch, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().setBool(settingSwitch, forKey: "whateverElseYouNeedExample")
}
}
}
Now when you want to save the users settings. I would would just it when app goes in-active.
First we will check if the settings have already been saved. If so, then we will just perform
an update. If not, we will save a new entry into yuor CoreData Entity.
add the following func to your appDelegate(if not already there):
func applicationDidEnterBackground(application: UIApplication) {
}
EDIT I HAVE TO GO TO WORK WILL COMPLETE REST OF ANWSER WHEN I GET BACK HOME. REALLY SORRY.

What's the correct way to zoom in on a users location using swift with xcode

I have a map that I'm trying to use to zoom in on a users location and I can seem to get setUserTrackingMode to work correctly. I have showsUserLocation working fine but I can't get it to zoom in. I'm using xCode 6 with iOS 8 and swift. Here's how I'm trying to call the method:
#IBOutlet var mapView : MKMapView
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.showsUserLocation = true
self.mapView.delegate = self;
self.mapView.setUserTrackingMode(MKUserTrackingModeFollow, animated: true);
I'm getting an error for self.mapView.setUserTrackingMode(MKUserTrackingModeFollow, animated: true);
The error says, "Use of unresolved identifier 'MKUserTrackingModeFollow'"
How can I get it to zoom in on the users location?
From the pre-release documentation the swift tracking modes are:
enum MKUserTrackingMode : Int {
case None
case Follow
case FollowWithHeading
}
You should use -
self.mapView.setUserTrackingMode(MKUserTrackingMode.Follow, animated: true);
In Swift, as enums are treated as a type, "Follow" is interpreted within the scope of an MKUserTrackingMode enum.

Resources