Different info for different pin annotations - mkmapview

I have two different pins placed on my mapview. I have an info button on each. The info buttons will segue to the a UIViewController that has a Image view (to hold a picture of the place) and a Text label ( To hold info about the place).
My problem is how can I generate the Info and picture depending on which pin annotation button was selected. The last function is the one used in order to segue to the info view controller.
class GetToTheStart: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
//map view outlet
#IBOutlet weak var mapView: MKMapView!
//defining use of location manager
let myLocMgr = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
//setting up location request
myLocMgr.desiredAccuracy = kCLLocationAccuracyBest
myLocMgr.requestWhenInUseAuthorization()
myLocMgr.startUpdatingLocation()
myLocMgr.delegate = self
mapView.delegate = self
// coordinates of desired locations for pins
var zoo1 = CLLocationCoordinate2DMake(53.347439, -6.291820)
var town1 = CLLocationCoordinate2DMake(53.347247, -6.290865)
//setting up pin 1 annotation (the zoo)
var zoopin = MKPointAnnotation()
zoopin.coordinate = zoo1
zoopin.title = "Dublin Zoo"
zoopin.subtitle = "This this the zoo"
mapView.addAnnotation(zoopin)
//setting up pin 2 annotation (the town)
var townpin = MKPointAnnotation()
townpin.coordinate = zoo1
townpin.title = "Dublin town"
townpin.subtitle = "This this the town"
mapView.addAnnotation(townpin)
}
//setting up Pin callout button for segue
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
}
let reuseIdentifier = "pin"
var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier) as? MKPinAnnotationView
if pin == nil {
pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
pin!.pinColor = .Red
pin!.canShowCallout = true
pin!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
} else {
pin!.annotation = annotation
}
return pin
}
//performing segue from info button to infoViewController
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
performSegueWithIdentifier("info", sender: view)
}

For this you need to override below method. Here we will get the annotationView which will trigger the segue.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "info") {
if let annotation = sender as? MKAnnotationView {
let detailViewController = segue.destinationViewController as! DetailViewController
detailViewController.titleText = annotation.annotation?.title ?? ""
detailViewController.detaileText = annotation.annotation?.subtitle ?? ""
}
}
}
And in the detailViewController is same as your infoViewController and here I have two labels and for that I have two public variables. This is just to avoid error because at this point we don't have the label objects.
Here is the code for my DetailViewController.
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var detailLabel: UILabel!
var titleText: String? { didSet { updateUI() } }
var detaileText: String? { didSet { updateUI() } }
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
private func updateUI() {
self.titleLabel?.text = self.titleText
self.detailLabel?.text = self.detaileText
}
}

Related

Swift 4 - How do I test for TextView content did change?

Swift 4, iOS 11 - I have a UITextView that is pre-populated with text but I want users to be able to save any changes they make to the content there. I also have a Save button in the navigation bar and I would like to disable it until the user actually changes the text in the TextView.
I know how to test for empty but I don't know how to test for when the text has been edited. How do I modify the following to test for changes to the content of TextView?
#IBAction func textEditingChanged(_ sender: UITextView) {
updateSaveButtonState()
}
func updateSaveButtonState() {
let descriptionText = descriptionTextView.text ?? ""
saveButton.isEnabled = !descriptionText.isEmpty
}
We'll to use it a dynamic way and not only in single place, i tried to make it easier to implement around the whole app, subclassing the UITextView is one of the only ways we got here #holex has suggested isEdited boolean flag and it gave me an idea, Thanks to that.
Here is the steps to implement it:
First of all set the defaultText of the textView and set the target of the method that will be called when the textView will be edited, so you can customize what ever you want.
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var textView: SBTextView!{
didSet{
textView.defaultText = "Hello"
textView.setTarget = (selector:#selector(self.updateSaveButtonState),target:self)
}
}
Lets say you'll setup the saveButton in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// setup save button action
saveButton.action = #selector(saveAction(_:))
saveButton.target = self
self.updateSaveButtonState()
}
And last is your save action and the selector to update the view using isEdited flag.
//MARK:- Actions
#objc private func updateSaveButtonState(){
// has not been changed keep save button disabled
if self.textView.isEdited == false{
self.saveButton.isEnabled = false
self.saveButton.tintColor = .gray
}else {
// text has been changed enable save button
self.saveButton.isEnabled = true
self.saveButton.tintColor = nil // will reset the color to default
}
}
#objc private func saveAction(_ saveButton:UIBarButtonItem){
self.textView.updateDefaultText()
}
TextView Custom Class:
//
// SBTextView.swift
//
//
// Created by Saad Albasha on 11/17/17.
// Copyright © 2017 AaoIi. All rights reserved.
//
import UIKit
class SBTextView: UITextView,UITextViewDelegate {
var isEdited = false
private var selector : Selector?
private var target : UIViewController?
var setTarget: (selector:Selector?,target:UIViewController?) {
get{
return (selector,target)
}
set(newVal) {
selector = newVal.0
target = newVal.1
}
}
var textViewDefaultText = ""
var defaultText: String {
get {
return textViewDefaultText
}
set(newVal) {
textViewDefaultText = newVal
self.text = newVal
self.isEdited = false
}
}
//MARK:- Life Cycle
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
self.setupTextview()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupTextview()
}
private func setupTextview(){
// setup textview
self.text = textViewDefaultText
self.delegate = self
}
func updateDefaultText(){
self.defaultText = self.text!
// update save button state
target!.perform(self.selector, with: nil, with: nil)
}
//MARK:- Delegate
internal func textViewDidChange(_ textView: UITextView) {
if textViewDefaultText != textView.text! {
isEdited = true
}else {
isEdited = false
}
// update save button state
target!.perform(self.selector, with: nil, with: nil)
}
}
I hope this helps.

How do I save data using NSKeyedArchiver?

I am very close to finishing my first iOS App using Swift 4 and iOS 11.
The app has a list displayed in a table view controller and a detail view with a UITextView object that is editable. My goal is for the user to be able to make edits to the content in the UITextView and save those changes using NSKeyedArchiver.
I have the list view complete and the detail view connected. You can make edits but they do not save.
I have confirmed that the entry does save to memory that persists beyond the session, but the edits do not save.
Reviews of documentation and working through multiple tutorials have not provided the insights needed. I have attached a screen shot to show the interface of the detail view and here is the code from the detail view controller where the save button triggers the Save action:
import UIKit
import os.log
class ViewController: UIViewController, UINavigationControllerDelegate, UITextViewDelegate {
var season: Season?
//MARK: Properties
#IBOutlet weak var seasonDetail: UITextView!
#IBAction func saveButton(_ sender: UIBarButtonItem) {
if let selectedDetail = seasonDetail.text {
seasonDetail.text = selectedDetail
} else {
print("failed to save changes.")
}
saveChanges()
print("Save button clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
title = season?.name
seasonDetail.text = season?.detail
seasonDetail.delegate=self
}
override func viewWillDisappear(_ animated: Bool) {
season?.detail = (seasonDetail?.text)!
}
func textViewDidEndEditing(_ textView: UITextView) {
seasonDetail.text = season?.detail
}
//MARK: UITextViewdDelegate
func textViewShouldReturn(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
func saveChanges() {
print("Saving items to: \(Season.ArchiveURL)")
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(season as Any, toFile: Season.ArchiveURL.path)
if isSuccessfulSave {
os_log("Season sucessfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save season.", log: OSLog.default, type: .debug)
}
}
}
Here is the code from the data model class:
import UIKit
import os.log
class Season: NSObject, NSCoding {
//MARK: Properties
var name: String
var detail: String
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("season")
//MARK: Types
struct PropertyKey {
static let name = "name"
static let detail = "detail"
}
//MARK: Initialization
init?(name: String, detail: String) {
guard !name.isEmpty else {
return nil
}
guard !detail.isEmpty else {
return nil
}
// Initialize stored properties
self.name = name
self.detail = detail
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(detail, forKey: PropertyKey.detail)
}
required convenience init?(coder aDecoder: NSCoder) {
// the name is required. If we cannnot get a name string, the initializer should fail.
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String
else {
os_log("Unable to decode the name for a Season object.", log: OSLog.default, type: .debug)
return nil
}
let detail = aDecoder.decodeObject(forKey: PropertyKey.detail)
self.init(name: name, detail: detail as! String)
}
}
My goal is to understand what is missing with my code and know how to persist all the data, including the edits. I would appreciate any direction that would help.
Please check :
class ViewController: UIViewController, UINavigationControllerDelegate, UITextViewDelegate {
var season: Season?
#IBOutlet weak var seasonDetail: UITextView!
#IBAction func saveButton(_ sender: UIBarButtonItem) {
if let selectedDetail = seasonDetail.text {
season?.detail = selectedDetail // this is the line
} else {
print("failed to save changes.")
}
saveChanges()
print("Save button clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
if season == nil {
season = Season(name: "Season Name", detail: "Season Details")
}
title = season?.name
seasonDetail.text = season?.detail
seasonDetail.delegate=self
}
override func viewWillDisappear(_ animated: Bool) {
season?.detail = (seasonDetail?.text)!
}
func textViewDidEndEditing(_ textView: UITextView) {
season?.detail = seasonDetail.text
}
//MARK: UITextViewdDelegate
func textViewShouldReturn(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
func saveChanges() {
print("Saving items to: \(Season.ArchiveURL)")
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(season as Any, toFile: Season.ArchiveURL.path)
if isSuccessfulSave {
os_log("Season sucessfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save season.", log: OSLog.default, type: .debug)
}
}
}

How do you show a blue dot instead of a Pin when showing current location in mapview?

How do you show a blue dot instead of a Pin when showing current location in map view? at the moment the code illustrates a red pin that shows the users current location as the user moves around. How can i convert this to the blue dot that apple use?
import UIKit
import MapKit
class ViewController: UIViewController,CLLocationManagerDelegate {
#IBOutlet weak var myMapView: MKMapView!
let myLocMgr = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
myLocMgr.desiredAccuracy = kCLLocationAccuracyBest
myLocMgr.requestWhenInUseAuthorization()
myLocMgr.startUpdatingLocation()
myLocMgr.delegate = self
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// get most recient coordinate
let myCoor = locations[locations.count - 1]
//get lat & long
let myLat = myCoor.coordinate.latitude
let myLong = myCoor.coordinate.longitude
let myCoor2D = CLLocationCoordinate2D(latitude: myLat, longitude: myLong)
//set span
let myLatDelta = 0.05
let myLongDelta = 0.05
let mySpan = MKCoordinateSpan(latitudeDelta: myLatDelta, longitudeDelta: myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoor2D, span: mySpan)
//center map at this region
myMapView.setRegion(myRegion, animated: true)
//add anotation
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoor2D
myMapView.addAnnotation(myAnno)
}
#IBAction func stop(sender: AnyObject) {
myLocMgr.stopUpdatingLocation()
}
#IBAction func resume(sender: AnyObject) {
myLocMgr.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
self.myMapView.showsUserLocation = true
showsUserLocation is what you need. It's MKMapView property set it in viewDidLoad or using IB (if possible). You don't need to do any extra stuff in LocationManager's delegate didUpdateLocations, just set it the MKMapView will do the rest of the stuff

Core Data in Swift

Trying to work with Core Data in Swift. Found this one example:
http://www.sep.com/sep-blog/2014/06/23/a-c-developer-learns-swift-part-1-core-data/
Created Entity "Person" with two string fields - lastname and firstname. Created UITableViewController (MainTableViewController) to display records on the screen. Created UIViewController (DetailViewController) to add new records. Created my own class (AddrBook) for entity data.
Does not work display the records contained in the entity in main class - MainTableViewController.
My class AddrBook.swift:
import UIKit
import CoreData
#objc(AddrBook)
class AddrBook: NSManagedObject {
#NSManaged var lastname:String
#NSManaged var firstname:String
}
UIViewController to add new records. DetailViewController.swift:
import UIKit
import CoreData
class DetailViewController: UIViewController {
#IBOutlet var lastNameField : UITextField = nil
#IBOutlet var firstNameField : UITextField = nil
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func saveButtonPressed(sender : AnyObject) {
let appDelegate:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDelegate.managedObjectContext
let projectEntity = NSEntityDescription.entityForName("Person", inManagedObjectContext: context)
var newPerson = AddrBook(entity: projectEntity, insertIntoManagedObjectContext: context)
newPerson.lastname = lastNameField.text
newPerson.firstname = firstNameField.text
context.save(nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Theoretically, in this class all goes well. Entry must be added.
The main class MainTableViewController.swift. To display the records. Trying to get them through the NSLog:
import UIKit
import CoreData
class MainTableViewController: UITableViewController {
init(style: UITableViewStyle) {
super.init(style: style)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest(entityName: "Person")
request.returnsObjectsAsFaults = false
let appDelegate:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDelegate.managedObjectContext
var results:NSArray = context.executeFetchRequest(request, error: nil)
for currentPerson in results as AddrBook[] {
NSLog("\(currentPerson.lastname)")
NSLog("\(currentPerson.firstname)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return 0
}
}
Shows that there is an error in the expression
for currentPerson in results as AddrBook[] {
Error:
Cannot convert the expression's type 'AddrBook[]' to type 'AddrBook[]'
What am I doing wrong?
for LombaX:
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest(entityName: "Person")
request.returnsObjectsAsFaults = false
let appDelegate:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDelegate.managedObjectContext
var results : AddrBook[]? = context.executeFetchRequest(request, error: nil) as? AddrBook[]
NSLog("\(results)")
if let array = results // check for nil and unwrap
{
for currentPerson in array as AddrBook[] {
NSLog("\(currentPerson.lastname)")
NSLog("\(currentPerson.firstname)")
}
}
// var results:NSArray = context.executeFetchRequest(request, error: nil)
/*for currentPerson in results as AddrBook[] {
NSLog("\(currentPerson.lastname)")
NSLog("\(currentPerson.firstname)")
}*/
}
Output NSLog - 2014-06-24 21:25:41.243 lesson-12-swift[1651:136375] nil
Variable results is nil :-(
Link to project in Dropbox: https://www.dropbox.com/s/q42rw5fw470timi/lesson-12-swift.zip
First, check that you filled the class in the data model:
As ProjectName.AddrBook (for swift classes you have to specify even the project name). (NOTE: this is needed only if you haven't used the prefix #objc(AddrBook) before the class, but I see that you used it, so this is not the problem).
or
as AddrBook as in this image in the Class section, top right
Moreover, change your cast like these:
// since executeFetchRequest can return nil, cast it as an optional array of [AddrBook]
// note: the first [AddrBook]? Can be omitted
var results : [AddrBook]? = context.executeFetchRequest(request, error: nil) as? [AddrBook]
if let array = results // check for nil and unwrap
{
for currentPerson in array as [AddrBook] {
// print
}
}
Or, less explicit and no check for nil
var results = context.executeFetchRequest(request, error: nil)
for currentPerson in results as [AddrBook] {
// print
}
let arrayresult = context!.executeFetchRequest(request, error: &error)
var arrayvalues=NSArray(array: arrayresult!)
for obj in arrayvalues as [AddrBook]
{
}

How do I detect Tap and Hold on a pin in MKMapView?

The title says it all. I'm trying to detect a tap on a pin in a MKMapView and I don't even know where to begin. Its not an UIView, so I can't add a gesture recognizer and I can't find a UIView in MKPlaceMark to add it.
You're question is not very clear but you can do like this:
import UIKit
import MapKit
protocol HandleMapSearch: class {
func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController {
func getDirections(){
// Here you can put anythings like:
guard let selectedPin = selectedPin else { return }
let mapItem = MKMapItem(placemark: selectedPin)
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMapsWithLaunchOptions(launchOptions)
}
}
extension ViewController : MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?{
guard !(annotation is MKUserLocation) else { return nil }
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
}
pinView?.pinTintColor = UIColor.orangeColor() // The pin's color
pinView?.canShowCallout = true // To set dialogue bubbles of the pin.
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare)) // To initialize the button in the dialogue bubbles of the pin.
button.addTarget(self, action: #selector(ViewController.getDirections), forControlEvents: .TouchUpInside) // To set and initialize the button.
pinView?.leftCalloutAccessoryView = button
return pinView
}
}
You can have more details in Thorn web site

Resources