Set route on a map using Indoo.rs ios sdk - ios

I am using Indoo.rs ios sdk by swift to build a small app showing my map, the issue is I have no idea to write a code to set route between 2 point on the map, I came across the documentation but no result ... here is my code :
The documentation is here : https://indoors.readme.io/docs/routing-1
Please if there is any idea share with me.
Thanks
import UIKit
import CoreLocation
class ViewController: UIViewController , RoutingDelegate ,IndoorsServiceDelegate, ISIndoorsSurfaceViewControllerDelegate ,IndoorsSurfaceViewDelegate {
#IBOutlet var SetRouteButton: UIButton!
var _currentBuilding: IDSBuilding?
var dot : ISIndoorsSurface?
var _indoorsSurfaceViewController: ISIndoorsSurfaceViewController?
override func viewDidLoad() {
super.viewDidLoad()
// API key of the cloud application
_ = Indoors(licenseKey: "My_API_KEY" , andServiceDelegate: nil)
_indoorsSurfaceViewController = ISIndoorsSurfaceViewController()
_indoorsSurfaceViewController!.delegate = self
// Enabling dotOnRail Mode
_indoorsSurfaceViewController!.surfaceView.dotOnRailsJumpingDistance = 55000
_indoorsSurfaceViewController!.surfaceView.enableDotOnRails = true
// show currunt postion
_indoorsSurfaceViewController!.surfaceView.showsUserPosition = true
// Load the map in a view holding it
addSurfaceAsChildViewController()
_indoorsSurfaceViewController!.loadBuildingWithBuildingId(MyBuildingId)
// Route snaping
Indoors.instance().enablePredefinedRouteSnapping()
// Display All Zones
_indoorsSurfaceViewController!.surfaceView.setZoneDisplayMode(IndoorsSurfaceZoneDisplayModeAllAvailable)
// Set visible map
let mapRect: CGRect = _indoorsSurfaceViewController!.surfaceView.visibleMapRect
_indoorsSurfaceViewController!.surfaceView.setVisibleMapRect(mapRect, animated: true)
// Filters
Indoors.instance().enableStabilisationFilter = true
Indoors.instance().stabilisationFilterTime = 4000
}
// Add the map to the view controller
func addSurfaceAsChildViewController () {
self.addChildViewController(_indoorsSurfaceViewController!)
//_indoorsSurfaceViewController!.view.frame = self.view.frame
_indoorsSurfaceViewController!.view.frame = CGRectMake(0 , 0, self.view.frame.width, self.view.frame.height * 0.7)
self.view.addSubview(_indoorsSurfaceViewController!.view)
_indoorsSurfaceViewController!.didMoveToParentViewController(self)
}
func buildingLoaded(building : IDSBuilding!) {
_currentBuilding = building
self.calculateRoute(_currentBuilding)
print("##################")
}
func calculateRoute(building : IDSBuilding!) {
let start = IDSCoordinate(x: 1, andY: 111, andFloorLevel: 0);
let end = IDSCoordinate(x: 1, andY: 111, andFloorLevel: 0);
let path = [start, end]
Indoors.instance().routeFromLocation(start, toLocation: end, inBuilding: building, delegate: self)
self.setRoute(path)
}
// MARK: RoutingDelegate
func setRoute(path: [AnyObject]!) {
_indoorsSurfaceViewController!.surfaceView.showPathWithPoints(path)
}
}
extension ViewController {
// MARK: ISIndoorsSurfaceViewControllerDelegate
func indoorsSurfaceViewController(indoorsSurfaceViewController: ISIndoorsSurfaceViewController!, isLoadingBuildingWithBuildingId buildingId: UInt, progress: UInt) {
NSLog("Building loading progress: %lu", progress)
}
func indoorsSurfaceViewController(indoorsSurfaceViewController: ISIndoorsSurfaceViewController!, didFinishLoadingBuilding building: IDSBuilding!) {
NSLog("Building loaded successfully!")
// By Mohammed Hassan
UIAlertView(title: "Indoors", message: "Building loaded successfully!", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "ok").show()
}
func indoorsSurfaceViewController(indoorsSurfaceViewController: ISIndoorsSurfaceViewController!, didFailLoadingBuildingWithBuildingId buildingId: UInt, error: NSError!) {
NSLog("Loading building failed with error: %#", error)
UIAlertView(title: error!.localizedDescription, message: error!.localizedFailureReason!, delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "ok").show()
}
// MARK: IndoorsServiceDelegate
func onError(indoorsError: IndoorsError!) {
}
func locationAuthorizationStatusDidChange(status: IDSLocationAuthorizationStatus) {
}
func bluetoothStateDidChange(bluetoothState: IDSBluetoothState) {
}
}

The documentation you linked shows that the points are not simple arrays of integers (that would be weird, imo). You're casting the array point to an array of AnyObjects (still not points, and since you used integers it won't work anyways) and pass that. You need to construct an array of IDSCoordinates (for start and end) to use as path. Like the documentation suggests (converted to swift):
var start = IDSCoordinate(x: 1234, andY: 1234, andFloorLevel: 0)
var end = IDSCoordinate(x: 12345, andY: 12345, andFloorLevel: 0)
// 1234 and so on are example values, that depends on your context
Indoors.instance().routeFromLocation(start, toLocation: end, delegate: self)
I haven't used the SDK (though I know the guys from indoo.rs, nice fellas), so I don't know when that delegate method setRoute is called exactly, the documentation example seems to update a view to show the path.
Edit: I deleted the setRoute call in my example, because on second thought I began to wonder why you were calling this delegate method in the first place. It's a bad name for a delegate method, true (more like a setter), but if I understand the documentation correctly, this is called for you, hence it's a delegate method. You're supposed to call Indoors.instance().routeFromLocation(start, toLocation: end, delegate: self). I assume, that then at some point calls your delegate's setRoute.

Related

I want to use "applicationWillEnterForeground", however my function requires an UIImageView and I can't call on it in AppDelegate?

I have an image on a website, and I want it to be put into a UIImageView, however I want it to refresh every time the application is launched.
I have the following code in a ViewController:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
get_image("WEBSITE.com/image.jpg", image)
}
func get_image(_ url_str:String, _ imageView:UIImageView)
{
let url:URL = URL(string: url_str)!
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: {
(
data, response, error) in
if data != nil
{
let image = UIImage(data: data!)
if(image != nil)
{
DispatchQueue.main.async(execute: {
imageView.image = image
imageView.alpha = 0
UIView.animate(withDuration: 2.5, animations: {
imageView.alpha = 1.0
})
})
}
}
})
task.resume()
}
}
Since the image in WEBSITE.com/image.jpg is going to change often, I wan't the app to pull the image everytime it launches. I did some research, and was told to put the following code into my AppDelegate.
func applicationWillEnterForeground(_ application: UIApplication) {
ViewController().get_image("WEBSITE.com/image.jpg", image)
}
However, after the link, the "image" doesn't work. How do I reference the image in the AppDelegate file?
BTW, I get the following error message:
"Cannot convert value of type 'module' to expected argument type 'UIImageView'"
What do I do?
You can use the observer pattern to let ViewController know when the app will enter the foreground.
UIScene has a built in notification that you can simply observe from within the viewDidLoad of your view controller.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(get_image), name: UIScene.willEnterForegroundNotification, object: nil)
}
The selector is the action taken whenever your view controller observes willEnterForegroundNotification. In order to use get_image as a selector you will also need to expose it to the Objective-C runtime by writing #Objc before the func keyword.

Referencing IBOutlet in another View Controller

So, I have been having some major trouble figuring this out and I have searched extensively for a solution but I surprisingly could not find one. I am attempting to create a multiple page (5, to be exact) Sign-Up for users.
I'll start off by showing you the layout of page 1 and 5 (since solving that issue will solve the issue for page 2-4):
Sign Up Page #1
Sign Up Page #5
As you may see (from the page control dots), I am using a page view controller to allow users to scroll from page to page. What I am trying to accomplish is giving the user the ability to enter their sign-up information in pages 1-5 before submitting it all at once (which can be located on page 5).
Here is the current code I am using for page #1:
class SignUpInfoViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is the current code I am using for page #5:
class TermsOfUseViewController: UIViewController {
let minPasswordCharCount = 6
#IBAction func signUpAction(_ sender: Any) {
let providedEmailAddress = SignUpInfoViewController().emailTextField.text!
let providedPassword = SignUpInfoViewController().passwordTextField.text!
let trimmedPassword = providedPassword.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if !(validEmail(enteredEmail: providedEmailAddress) && validPassword(enteredPassword: trimmedPassword)) {
invalidCredentialsAlert()
}
else {
FIRAuth.auth()?.createUser(withEmail: providedEmailAddress, password: providedPassword) { user, error in
if error == nil {
FIRAuth.auth()!.signIn(withEmail: providedEmailAddress,
password: providedPassword)
}
else {
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
// Email is valid if it has a standard email format
func validEmail(enteredEmail: String) -> Bool {
let emailFormat = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %#", emailFormat)
return emailPredicate.evaluate(with: enteredEmail)
}
// Password is valid if it is not empty or greater than a specified number of characters
func validPassword(enteredPassword: String) -> Bool {
if (enteredPassword != "" && enteredPassword.characters.count >= minPasswordCharCount) {
return true
}
return false
}
In the TermsOfUseViewController class, I am attempting to use the emailTextField and passwordTextField outlets from the SignUpInfoViewController, but I am receiving the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I debugged the error and saw that the emailTextField property from SignUpInfoViewController is nil and so force unwrapping it will cause the app to crash (Note: I have correctly connected the IBOutlets to the SignUpInfoViewController, so no issue there).
How can I safely transfer the usage of the IBOutlets from the SignUpInfoViewController class to the TermsOfUseViewController class without it crashing? In other words, how can I make it to where the IBOutlets are no longer nil when I reference them in the TermsOfUseViewController class?
Thank you!
That is a perfect scenario for delegate pattern
protocol SignUpProtocol: class {
func didProvideUserData(username: String ,password: String)
}
In your signup class declare a delegate: public weak var delegate:SignUpProtocol?
I am assuming when the user has provided the require info, they need to press some button to go to the next step: Thus in that button you should raise the delegate
#IBAction func nextButton(sender:UIButton) {
guard let username = usernameTextfield?.text, let password = passwordTextField?.text, else { fatalError("textfields were empty") }
if delegate != nil { // this saying when someone is listening to me, I will expose any method associated to me
delegate?.didProvideUserData(username:username, password:password) // passing the username and password from textfield
}
}
if you don't have a button, then look at property observer, where you could have some property
var didFulfill:Bool? = nil {
didSet {
if didFulfill != nil && didFulfill == true {}
// here you check if your textfields are sets then raise the delegate
}
}
set this property didFulfill = when both textfields are not empty :)
Now in your Terms class, just subscribe to that delegate
class TermsOfUseViewController: UIViewController, SignUpProtocol {
var signUpVc: SignUpInfoViewController?
override func viewDidLoad() {
super.viewDidLoad()
signUpVc = SignUpInfoViewController()
signUpVc?.delegate = self
}
func didProvideUserData(username: String, password:String) {
// there is your data
}
}
You have to take in account that you don't have all references for all UIPageViewControllers all the time. That being said, I would suggest either to keep object in UIPageViewController with updated information or using Singleton Pattern to use it to store info into it and later use it. UIPageViewController are being reused and you might have one before and one after and relying onto having them would be wrong.
You can use UIPageViewController as self.parentViewController or something like that.

Errors while implementing Chromecast in swift 3

I seem to have a problem implementing ChromeCast features in a project of mine.
I have been trying to implement the GCKDeviceScannerListener Singleton Class on a UIViewController, however its delegate methods are not getting called.
The deviceDidComeOnline method of GCKDeviceScannerListener never gets called.
Instead I have a bunch of error displayed by the chromeCast logger as followed:
+[NSMutableDictionary(GCKAdditions) gck_loadFromCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_nearby_devices.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_loadFromCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_network_cache.plist doesn't exist.
scanning started
+[NSMutableDictionary(GCKAdditions) gck_deleteCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_deleteCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache_v1.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_deleteCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache_v2.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_loadFromCacheWithName:] - Device cache file
file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache_v3.plist doesn't exist
I can't seem to figure out why I have these errors. But it seems that the deviceScanner never even finds my receiver device.
My viewController code is:
class ChromeCastViewController: UIViewController, GCKDeviceScannerListener, GCKDeviceManagerDelegate, GCKMediaControlChannelDelegate{
fileprivate let kCancelTitle = "Cancel"
fileprivate let kDisconnectTitle:String! = "Disconnect"
// Publicly available receiver to demonstrate sending messages - replace this with your
// own custom app ID.
fileprivate let kReceiverAppID = "XXXXXXXXX"
fileprivate lazy var btnImage:UIImage = {
return UIImage(named: "icon-cast-identified.png")!
}()
fileprivate lazy var btnImageselected:UIImage = {
return UIImage(named: "icon-cast-connected.png")!
}()
fileprivate var deviceScanner:GCKDeviceScanner?
fileprivate var deviceManager:GCKDeviceManager?
fileprivate var mediaInformation:GCKMediaInformation?
fileprivate var selectedDevice:GCKDevice?
#IBOutlet weak var googleCastButton: UIBarButtonItem!
#IBOutlet weak var backButton: UIBarButtonItem!
#IBAction func backAction(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
self.startScan()
}
func startScan() {
let filterCriteria = GCKFilterCriteria(forAvailableApplicationWithID: kReceiverAppID)
self.deviceScanner = GCKDeviceScanner(filterCriteria: filterCriteria)
if let deviceScanner = self.deviceScanner {
deviceScanner.add(self)
deviceScanner.startScan()
print("scanning started")
deviceScanner.passiveScan = true
}
}
// MARK: GCKDeviceScannerListener
func deviceDidComeOnline(_ device: GCKDevice) {
print("deviceDidComeOnline")
print("Device found: \(device.friendlyName)");
self.updateButtonStates()
}
func deviceDidGoOffline(_ device: GCKDevice) {
print("deviceDidGoOffline()")
print("Device went away: \(device.friendlyName)");
self.updateButtonStates()
}
func deviceDidChange(_ device: GCKDevice) {
print("deviceDidChange()");
}
func updateButtonStates() {
print("updateButton")
if (deviceScanner!.devices.count > 0) {
// Show the Cast button.
navigationItem.rightBarButtonItems = [googleCastButton!]
if (deviceManager != nil && deviceManager?.connectionState == GCKConnectionState.connected) {
// Show the Cast button in the enabled state.
googleCastButton!.tintColor = UIColor.blue
} else {
// Show the Cast button in the disabled state.
googleCastButton!.tintColor = UIColor.gray
}
} else{
// Don't show Cast button.
navigationItem.rightBarButtonItems = []
}
}
}
Thank you in advance for any help or tips you can give me.
Best regards
UPDATE:
I have modified my code base to follow google v3 guidelines.
I now instantiate a GCKCastContext in AppDelegate in order to use google widgets.
But It seems that GCKCastContext or functionalities associated with the singleton are never called after initialising it. I have tried to add the GCKDiscoveryManagerListener to my AppDelegate to see if was detecting my ChromeCast device.
The code for my AppDelegate is :
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate,GCKDiscoveryManagerListener {
var window: UIWindow?
fileprivate let kReceiverAppID = "XXXXXXXXXX"
var discoveryManager: GCKDiscoveryManager
override init(){
let options:GCKCastOptions = GCKCastOptions(receiverApplicationID: kReceiverAppID)
GCKCastContext.setSharedInstanceWith(options)
self.discoveryManager = GCKCastContext.sharedInstance().discoveryManager
super.init()
self.discoveryManager.add(self)
self.discoveryManager.passiveScan = true
self.discoveryManager.startDiscovery()
GCKLogger.sharedInstance().delegate = self
}
.....
func log(fromFunction function: UnsafePointer<Int8>, message: String) {
let functionName = String(cString: function)
print(functionName + " - " + message);
}
func didUpdateDeviceList() {
print("didUpdateDeviceList with \(discoveryManager.deviceCount) devices")
(0..<discoveryManager.deviceCount).forEach { index in
print(index, discoveryManager.device(at: index))
}
}
func didStartDiscoveryForDeviceCategory(deviceCategory: String) {
print("GCKDiscoveryManagerListener: \(deviceCategory)")
print("FOUND: \(self.discoveryManager.hasDiscoveredDevices)")
}
func willUpdateDeviceList(){
print("will update device was called")
}
}
The functions didUpdateDeviceList, didStartDiscoveryForDeviceCategory
, willUpdateDeviceList are never called, meaning no Chrome device is ever found making the widgets unusable.
Thank again for any help

how to match (or compare) taps to annotations?

EnvironmentXcode 8Swift 3
Problem Statement
I want to be able to determine if a user taps on a MKPointAnnotation and then extract information (like title and subtitle) from that annotation for use within my app.
I imagine this is not terribly difficult, but I'm a bit lost in terms of what I need to do / what various classes / objects / methods / etc. I need to use to do this.So I'm looking for pointers / guidance - code is welcome, but at this point the pointers / guidance would be a significant step forward for me.
Code SnippetsAbridged version of the code thus far (trying to limit it to just the relevant pieces)
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
//... various #IBOutlet's for text fields, buttons, etc. ...
#IBOutlet weak var map: MKMapView!
var coords: CLLocationCoordinate2D?
var locationManager: CLLocationManager = CLLocationManager()
var myLocation: CLLocation!
var annotation: MKPointAnnotation!
var annotationList: [MKPointAnnotation] = []
var matchingItems: [MKMapItem] = [MKMapItem]()
override func viewDidLoad() {
super.viewDidLoad()
//... text field delegates, and other initilizations ...
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
}
myLocation = nil
//... other initializations...
}
// Search for things that match what my app is looking for ("<search string>")
func performSearch() {
annotationList.removeAll() // clear list
matchingItems.removeAll() // clear list
var closest = MKMapItem()
var distance = 10000.0
let request = MKLocalSearchRequest()
let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
request.naturalLanguageQuery = "<search string>"
request.region = MKCoordinateRegionMake(myLocation.coordinate, span)
let search = MKLocalSearch(request: request)
if search.isSearching {
search.cancel()
}
search.start(completionHandler: {
(_ response, _ error) in
if error != nil {
self.showAlert(msg: "Error occurred in search\nERROR: \(error?.localizedDescription)")
}
else if response!.mapItems.count == 0 {
self.showAlert(msg: "No matches found")
}
else {
for item in response!.mapItems {
// Track the closest placemark to our current [specified] location
let (distanceBetween, prettyDistance) = self.getDistance(loc1: self.myLocation, loc2: item.placemark.location!)
let addrObj = self.getAddress(placemark: item.placemark)
//... some code omitted ...
// Add markers for all the matches found
self.matchingItems.append(item as MKMapItem)
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
annotation.subtitle = "\(addrObj.address!) (\(prettyDistance))"
self.map.addAnnotation(annotation)
self.annotationList.append(annotation)
}
//... some code omitted ...
}
})
}
//... code for getDistance(), getAddress() omitted for brevity - they work as designed ...
//... other code omitted as not being relevant to the topic at hand
}
I imagine that I will need to override touchesEnded and possibly touchesBegan and maybe touchesMoved in order to detect the tap.
What I cannot figure out is how to compare a touch's location (represented as X/Y coordinates on the screen) to an MKPointAnnotation's or MKMapItem's location (which is represented as latitude/longitude coordinates on a map)
So - that's kind of where I'm currently stuck. I searched various terms on the web but wasn't able to find anything that [simplisticly] answerwed my question - and in Swift code format (there were a number of postings that looked like they might help, but the code presented wasn't in Swift and I don't do the translation that easily).
UPDATE (19:48 ET)
I found this article: How do I implement the UITapGestureRecognizer into my application and tried to follow it, but ...
I modified the code a bit (added UIGestureRecognizerDelegate):
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) //<<<== See notes below
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
print("A")//#=#
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
print("B")//#=#
}
func handleTap(tap: UITapGestureRecognizer) {
print("ARRIVED")//#=#
let here = tap.location(in: map)
print("I AM HERE: \(here)")//#=#
}
//...
}
With regard to the declaration / definition of tapHandler, I tried the following:
let tapHandler = UITapGestureRecognizer(target: self, action: "handleTap:")
let tapHandler = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) // supresses warning
The first two caused a warning to show up in Xcode, the last simply supresses the warning:
[W] No method declared with Objective-C selector 'handleTap:'
When I run my app and tap on a pin - I get the following in my log:
A
B
libc++abi.dylib: terminating with uncaught exception of type NSException
Which would seem (to me) to indicate that the general setup in viewDidLoad is okay, but as soon as it tries to handle the tap, it dies without ever getting to my handleTap function - and thus the warning (shown above) would seem to be far more serious.
So, I'm not sure if I can count this as making progress, but I'm trying...
Thanks to this MKAnnotationView and tap detection I was able to find a solution. My code changes from those originally posted:
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer() //<<<== No parameters
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
}
// Not sure who calls this and requires the Bool response, but it seems to work...
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return self.handleTap(touch: touch).count > 0
}
// Major Changes
private func handleTap(touch: UITouch) -> [MKAnnotationView] {
var tappedAnnotations: [MKAnnotationView] = []
for annotation in self.map.annotations {
if let annotationView: MKAnnotationView = self.map.view(for: annotation) {
let annotationPoint = touch.location(in: annotationView)
if annotationView.bounds.contains(annotationPoint) {
self.name.text = annotationView.annotation?.title!
let addr = AddrInfo(composite: ((annotationView.annotation?.subtitle)!)!)
self.address.text = addr.street!
self.city.text = addr.city!
self.state.text = addr.state!
self.zipcode.text = addr.zip!
tappedAnnotations.append(annotationView)
break
}
}
}
return tappedAnnotations
}
//...
}
The AddrInfo piece is my own little subclass that, among other things, takes a string like "1000 Main St., Pittsburgh, PA 15212, United States" and breaks it into the individual pieces so that they can be accessed, well, individually (as indicated in the code above).
There might be an easier, or better, way to achieve what I was looking for - but the above does achieve it, and so I consider it to be the answer for my issue.

Delegate: MKMapView is nil, though it is initialized

When I receive in my Bluetooth class (new) values from my device, then I call a delegate. So the Bluetooth class is running in the background.
My protocol is simple:
protocol RefreshPositionInDrive {
func changingValue(latitude: Double, longitude: Double)
}
In my UIViewController I initialize a map. When I worked at the beginning without delegates this code works fine.
func initializeMapResolution() {
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: 50.910349, longitude: 8.066895)
let coordinateRegion = MKCoordinateRegionMakeWithDistance(initialLocation.coordinate,
regionRadius * 1.5, regionRadius * 1.5)
MapDrive.setRegion(coordinateRegion, animated: true)
MapDrive.delegate = self
}
My method from my protocol:
func changingValue(latitude: Double,longitude: Double) {
print("THE NEW COORDINATES \(latitude) \(longitude)")
if self.MapDrive == nil {
print("Is nil")
} else {
updateTheMap()
}
}
Output:
THE NEW COORDINATES 25.012x 16.992
Is nil
But I don't understand that. I initialize my map first. After that the changingValue is called. How can be the MapDrive nil?
I tested the code without delegates, just with some fix coordinates in my UIViewController and the annotation appears.
(I'm working the first time with delegates.)
EDIT
I was indistinctly: My MapDrive:
#IBOutlet weak var MapDrive: MKMapView!
So I can't instantiate like you mean or?
You'll want to reference a MapDrive instance to the UIViewController, your MapDrive is probably released when your function ends.
class UIViewController {
var mapDrive = MapDrive()
func initializeMapResolution() {
// Instantiate map drive, assign it to self.mapDrive
//then assign the delegate of the property on self.
self.mapDrive.delegate = self
}
}

Resources