I have two Controller which is Map and BottomSheet.
Once i click marker in Map I want to change UIlabel text.
BottomsheetController.swift
var Data : [DataClass] = []
class BottomSheetViewController : UIViewController{
#IBOutlet weak var Id: UILabel!
func getid(id : String?){
let kick = id
self.Id.text = kick
}
}
MapViewController.swift
class MapViewController: UIViewController ,GMUClusterManagerDelegate{
#IBOutlet weak var mapView: GMSMapView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addBottomSheetView()
}
// MARK: Click marker
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
if let poiitem = marker.userData as? POIItem {
let bvc = BottomSheetViewController()
bvc.getimei(imei: Data[0].id)
}
return true
}
}
it keeps saying Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Just use segue for passing data between two controller
performSegue(idnitfierName,sender)
And than,
perform(){
let vc = segue.destination as! MapViewController
vc.variableName = variable That you have to pass
}
Related
I am doing a Mac application, and I have a problem appending text to a NSScrollView when I call a function from a different class.
I have this function on my ViewController class:
import Cocoa
class PopoverVC1: NSViewController {
let popover1 = NSPopover()
class func loadView() ->PopoverVC1 {
let vc = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),
bundle: nil).instantiateController(withIdentifier:
NSStoryboard.SceneIdentifier(rawValue: "Popover1")) as! PopoverVC1
vc.popover1.contentViewController = vc
return vc
}
override func viewDidLoad() {
super.viewDidLoad()
popover1.behavior = .transient
popover1.contentViewController = self
}
func showPopover (view: NSView){
popover1.show(relativeTo: view.bounds, of: view, preferredEdge: .maxY)
}
#IBOutlet weak var radioOption1: NSButton!
#IBOutlet weak var radioOption2: NSButton!
#IBOutlet weak var radioOption3: NSButton!
#IBAction func clickOption(_ sender: NSButton) {
switch sender {
case radioOption1: popover1.performClose(sender)
case radioOption2: let vc = ViewController()
vc.myPrint(string: "This is a test")
default: print ("hello")
}
}
}
Than I have a PopoverVC1 class, which is a class to a popover I am using:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var oneYes: NSButton!
#IBOutlet weak var oneNo: NSButton!
#IBOutlet weak var notesArea: NSScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded
}
}
func myPrint (string: String){
let mystring = string
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: mystring)
text?.append(attr)
}
let popover1 = NSPopover()
#IBAction func oneClicked(_ sender: NSButton) {
switch sender {
case oneYes: let vc = PopoverVC1.loadView()
vc.showPopover(view: sender)
case oneNo:
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: "test")
text?.append(attr)
default: print ("")
}
}
}
However, I got an error when I press the radio button "oneNo" that should call the function "myPrint" and pass the argument.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I did some tests and when I call this same function "myPrint" from within the ViewCotroller class it works fine.
Any ideas?
Your issue is in clickOption when you are calling:
let vc = ViewController()
vc.myPrint(string: "This is a test")
When you call this method from code and the ViewController's UIViews are set up in a storyboard, the connection from the storyboard is not made. That is why the notesArea is nil when you call the function myPrint. In this case you are creating a new copy of ViewController and it will not be the same one that created the popover.
There are a few ways you can solve the problem that you are trying to accomplish. One of them is known as a delegate. This is a way for you to to call the ViewController's methods like your popover inherited them. You can check out a tutorial here. The idea is that we want to have a reference to the ViewController in your popover so that you can call the functions in the protocol. Then the ViewController that conforms to the protocol will be responsible for handling the method call.
So let's create a protocol called PrintableDelegate and have your ViewController class conform to it. Then in your popover, you will be able to have a reference to the ViewController as a weak var called delegate (you can use what ever name you want but delegate is standard). Then we can call the methods described in the protocol PrintableDelegate, by simply writing delegate?.myPrint(string: "Test"). I have removed some of your irrelevant code from my example.
protocol PrintableDelegate {
func myPrint(string: String)
}
class ViewController : UIViewController, PrintableDelegate {
func myPrint (string: String){
let mystring = string
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: mystring)
text?.append(attr)
}
#IBAction func oneClicked(_ sender: NSButton) {
let vc = PopoverVC1.loadView()
// Set the delegate of the popover to this ViewController
vc.delegate = self
vc.showPopover(view: sender)
}
}
class PopoverVC1: NSViewController {
// Delegates should be weak to avoid a retain cycle
weak var delegate: PrintableDelegate?
#IBAction func clickOption(_ sender: NSButton) {
// Use the delegate that was set by the ViewController
// Note that it is optional so if it was not set, then this will do nothing
delegate?.myPrint(string: "This is a test")
}
}
I have a view controller embedded in another VC.
I would like to get the value of a variable from the main VC inside the embedded one. Specifically, I would like to change the text of label2 based on the value of label1.
I tried with "prepareForSegue", but it seems it's not triggered for embedded view controllers. I tried to isolate the problem in a test project:
Code for main VC:
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
}
Code for embedded VC:
class EmbeddedVC: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Thanks for your help :)
A way to achiŠµve this is to get the child view controller instance in the parent's viewDidLoad. It appears that the parent's viewDidLoad: gets called after the child's viewDidLoad:, which means the label is already created in the child's view.
override func viewDidLoad() {
super.viewDidLoad()
if let childVC = self.childViewControllers.first as? ChildVC {
childVC.someLabel.text = "I'm here. Aye-aye."
}
}
First of all you can't set directly EmbeddedVC's lable2.text In prepareForSegue
because call sequence following below
MainVC's prepareForSeque this time EmbeddedVC's label2 is nil
EmbeddedVC's viewDidLoad called then label2 loaded
MainVC's viewDidLoad called then label1 loaded
so if you assign MainVC's label1.text to EmbeddedVC's label2.text in prepareForSeque
both label1 and label2 are nil so did not work
There are two way to solve this question
First Solution
MainViewController has EmbeddedVC and when MainVC's viewDidLoad called, assign label1.text to embeddedVC.label2.text
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
var embeddedVC: EmbeddedViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
embeddedVC?.label2.text = label1.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
self.embeddedVC = embeddedVC
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Second Solution, use protocol and get MainVC's label text when viewWillAppear or viewDidAppear (later viewDidLoad called)
protocol EmbeddedVCDelegate: class {
func labelText() -> String?
}
class MyViewController: UIViewController, EmbeddedVCDelegate {
#IBOutlet weak var label1: UILabel!
// MARK: EmbeddedVCDelegate
func labelText() -> String? {
return label1.text
}
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
embeddedVC.delegate = self
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
weak var delegate: EmbeddedVCDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
override viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
label2.text = delegate?.labelText()
}
}
You should try to use prepareForSegue like this:
if segue.identifier == "identifier" {
guard let destinationViewController = segue.destination as? VC2 else { return }
destinationViewController.label2.text = mytext
}
Where the segue identifier you assign in storyboard
I am trying to pass the data of user-input coordinates from one VC to another using Notifications. When I run the code, it does not add an annotation onto the MapView, so I have a hunch I may have not set up the notification to send with the inputted coordinates properly, but I am not sure where I have went wrong.
Class file that takes the input of coordinates:
import UIKit
import CoreLocation
var locations: [Dictionary<String, Any>] = [] // here I initialize my empty array of locations
class OtherVC: UIViewController {
#IBOutlet weak var latitudeField: UITextField!
#IBOutlet weak var longitudeField: UITextField!
#IBOutlet weak var titleTextField: UITextField!
var coordinates = [CLLocationCoordinate2D]()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addToMap(_ sender: Any) {
let lat = latitudeField.text!
let long = longitudeField.text!
let title = titleTextField.text!
var location: [String: Any] = ["title": title, "latitude": lat, "longitude": long]
locations.append(location)
NotificationCenter.default.post(name: NSNotification.Name("MapViewController.coordinate.updated"), object: locations, userInfo: nil)
}
}
Class file that receives notification and places annotation:
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
func add(notification: NSNotification) {
let dict = notification.object as! NSDictionary
// takes the coordinates from the notification and converts such that the map can interpret them
let momentaryLat = (dict["latitude"] as! NSString).doubleValue
let momentaryLong = (dict["longitude"] as! NSString).doubleValue
let annotation = MKPointAnnotation()
annotation.title = dict["title"] as? String
annotation.coordinate = CLLocationCoordinate2D(latitude: momentaryLat as CLLocationDegrees, longitude: momentaryLong as CLLocationDegrees)
mapView.addAnnotation(annotation)
self.mapView.centerCoordinate = annotation.coordinate
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "pinAnnotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
}
annotationView?.annotation = annotation
return annotationView
}
}
It's the same as the Objective-C API, but uses Swift's syntax.
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(batteryLevelChanged),
name: UIDeviceBatteryLevelDidChangeNotification,
object: nil)
Or in Swift 3:
NotificationCenter.default.addObserver(
self,
selector: #selector(self.batteryLevelChanged),
name: .UIDeviceBatteryLevelDidChange,
object: nil)
If your observer does not inherit from an Objective-C object, you must prefix your method with #objc in order to use it as a selector.
#objc func batteryLevelChanged(notification: NSNotification){
//do stuff
}
Register obeserver to receive notification in your MapViewController
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.add(_:)), name: NSNotification.Name("MapViewController.coordinate.updated"), object: nil)
}
Also you can send data using userInfo property of NSNotification or send using custom object.
func add(_ notification: NSNotification) {
...
}
see pass data using nsnotificationcenter
I am trying to construct a Map App that can receive user inputs of latitude and longitude coordinates that when entered, will place a pin on a map in a different tab. My FirstVC consists of a button "Add Locations" that segues to OtherVC which the user can input the coordinates. SecondVC consists of the MapView. My initial idea is to have a specific array of coordinates, and any new coordinates will be appended to this array. The execution is where I am lacking, because I am not sure how to transfer this array to the MapView. Here is what I have so far:
For the input of coordinates:
import UIKit
import CoreLocation
class OtherVC: UIViewController {
#IBOutlet weak var latitudeField: UITextField!
#IBOutlet weak var longitudeField: UITextField!
var coordinates = [CLLocationCoordinate2D]()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addToMap(_ sender: Any) {
let lat = Double(latitudeField.text!)
let long = Double(longitudeField.text!)
self.coordinates.append(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
}
}
For the MapView:
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var coordinates = [CLLocationCoordinate2D]() {
didSet {
// Update the pins
// Since it doesn't check for which coordinates are new, it you go back to
// the first view controller and add more coordinates, the old coordinates
// will get a duplicate set of pins
for (index, coordinate) in self.coordinates.enumerated() {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = "Location \(index)"
mapView.addAnnotation(annotation)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "pinAnnotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
}
annotationView?.annotation = annotation
return annotationView
}
}
I think you need is get your second ViewController MapViewController from your tabBarController and then pass the coordinates array, so in your addToMap Action replace with this
#IBAction func addToMap(_ sender: Any) {
let lat = Double(latitudeField.text!)
let long = Double(longitudeField.text!)
self.coordinates.append(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
//here we pass the coordinate array to mapViewController
if let mapViewController = self.tabBarController?.viewControllers?[1] as? MapViewController
{
mapViewController.coordinates = self.coordinates
}
}
You need also add a navigation controller, like in the picture
I hope this helps you
Generally speaking I only use the method of directly passing the data from one ViewController to the next if there is a parent child relationship and I can do it in prepareForSegue or in and unwind (child to parent). Otherwise I think its better to use the publisher subscriber model with Notification. When your coordinate changes you post a Notification:
NotificationCenter.default.post(name: NSNotification.Name("MapViewController.coordinate.updated"), object: self, userInfo: nil)
Now anyone who cares about MapViewController's coordinates changing can listen:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(coordinateUpdated), name: NSNotification.Name("coordinate.updated"), object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc private func coordinateUpdated( notification: Notification) {
if let source = notification.object as? MapViewController {
print(source.coordinates)
}
}
This makes the views loosely coupled and MapViewController doesn't need to care about who needs to be updated; the subscribers are responsible for registering themselves.
Please help, Im having trouble passing parse object from annotation Query to destinationViewController. When the user taps the annotation view it takes the user to the destinationVC, but it passes the same object no matter which annotation the user taps. I think it has something to do with the prepareForSegue function. Please let me know what Im doing wrong.
Heres my code:
class MapSearchViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var jobPosts = [PFObject]()
var jobPost: PFObject?
let locationManager = CLLocationManager()
var currentLoc: PFGeoPoint! = PFGeoPoint()
override func viewDidLoad() {
super.viewDidLoad()
//MARK new added code Start--
self.mapView.delegate = self
self.mapView.setUserTrackingMode(MKUserTrackingMode.Follow, animated: true)
//MARK new added code End--
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
//Snippet For Blue Current Location Dot:
//self.mapView.showsUserLocation = true
}
//MARK: -- Location Delegate Methods
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Errors: " + error.localizedDescription)
}
//MARK: OCT 26
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "JobPost")
currentLoc = PFGeoPoint(location: locationManager.location)
annotationQuery.whereKey("location", nearGeoPoint: currentLoc, withinMiles: 100)
annotationQuery.findObjectsInBackgroundWithBlock {
(posts, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
let jobPosts = posts as [PFObject]!
for post in jobPosts {
let point = post["location"] as! PFGeoPoint
var annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
self.mapView.addAnnotation(annotation)
annotation.title = post["job"] as! String
let pay = post["price"] as! String
let payLabel = "\(pay)$"
annotation.subtitle = payLabel
let btn = UIButton(type: .DetailDisclosure)
self.jobPost = post
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
var view = mapView.dequeueReusableAnnotationViewWithIdentifier("annotationIdentifier")
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "annotationIdentifier")
view?.canShowCallout = true
view?.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
} else {
view?.annotation = annotation
}
return view
}
var selectedAnnotation: MKPointAnnotation!
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
selectedAnnotation = view.annotation as? MKPointAnnotation
performSegueWithIdentifier("annotationSender", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? JobDetailViewController {
destination.currentObject = jobPost
}
}
}
code for detailVC:
class JobDetailViewController: UIViewController, MFMailComposeViewControllerDelegate {
#IBOutlet weak var jobTitleLabel: UILabel!
#IBOutlet weak var priceLabel: UILabel!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var jobDescriptionContent: UITextView!
#IBOutlet weak var pictureImageView: UIImageView!
var jobPosts:[PFObject]!
var currentObject : PFObject?
var distance = "7"
var annotation = MKPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
self.distanceLabel.text = "\(distance) miles away"
if let object = currentObject {
jobTitleLabel.text = object["job"] as! String
priceLabel.text = object["price"] as! String
jobDescriptionContent.text = object["description"] as! String
}
}