I'm currently trying to implement a Map connected with a search function. For the overlay containing the table view, I've decided to go for a library called FloatingPanel. Anyways, this shouldn't be of importance, since it shouldn't affect the essential code etc.
The data is being read inside of SearchResultsTableViewController and passed to MapViewController by passData().
Inside of passData() a function called addAnnotationToMap() from MapViewController is being called to process the data - printing inside of the function will always return the correct value.
Inside of the function I'm trying to add an annotation to the map, but it won't work. Nothing happens - but when I do print(mapView.annotations) the Array of annotations is being returned including mine.
By the way, I had to add loadViewIfNeeded() to the MapViewController because without (after using the overlay view) mapView returned nil.
Sorry for the amount of code - but there might be some relevant code. I didn't include the code for the table view.
MapViewController
class MapViewController: UIViewController, FloatingPanelControllerDelegate, UISearchBarDelegate {
var fpc: FloatingPanelController!
var searchVC = SearchResultTableViewController()
private enum AnnotationReuseID: String {
case pin
}
#IBOutlet private var mapView: MKMapView!
var mapItems: [MKMapItem]?
var boundingRegion: MKCoordinateRegion?
override func viewDidLoad() {
super.viewDidLoad()
if let region = boundingRegion {
mapView.region = region
}
fpc = FloatingPanelController()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.cornerRadius = 9.0
fpc.surfaceView.shadowHidden = false
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
// Set a content view controller
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Add FloatingPanel to a view with animation.
fpc.addPanel(toParent: self, animated: true)
fpc.move(to: .tip, animated: true)
// Must be here
searchVC.searchController.searchBar.delegate = self
}
func addAnnotationToMap() {
loadViewIfNeeded()
guard let item = mapItems?.first else { return }
guard let coordinate = item.placemark.location?.coordinate else { return }
let annotation = MKPointAnnotation()
annotation.title = item.name
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
searchBar.showsCancelButton = false
fpc.move(to: .tip, animated: true)
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = true
searchVC.tableView.alpha = 1.0
fpc.move(to: .full, animated: true)
searchVC.hideHeader()
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
fpc.move(to: .half, animated: true)
searchVC.showHeader()
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
let y = vc.surfaceView.frame.origin.y
let tipY = vc.originYOfSurface(for: .tip)
if y > tipY - 44.0 {
let progress = max(0.0, min((tipY - y) / 44.0, 1.0))
self.searchVC.tableView.alpha = progress
}
}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
if vc.position == .full {
searchVC.searchBar.showsCancelButton = false
searchVC.searchBar.resignFirstResponder()
}
}
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .allowUserInteraction,
animations: {
if targetPosition == .tip {
self.searchVC.tableView.alpha = 0.0
self.searchVC.hideHeader()
} else if targetPosition == .half {
self.searchVC.tableView.alpha = 1.0
self.searchVC.showHeader()
} else {
self.searchVC.tableView.alpha = 1.0
self.searchVC.hideHeader()
}
}, completion: nil)
}
}
SearchViewController
class SearchResultTableViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var visualEffectView: UIVisualEffectView!
private enum CellReuseID: String {
case resultCell
}
private var places: [MKMapItem]? {
didSet {
tableView.reloadData()
}
}
private var suggestionController: SuggestionsTableTableViewController!
var searchController: UISearchController!
private var localSearch: MKLocalSearch? {
willSet {
places = nil
localSearch?.cancel()
}
}
private var boundingRegion: MKCoordinateRegion?
override func awakeFromNib() {
super.awakeFromNib()
suggestionController = SuggestionsTableTableViewController()
suggestionController.tableView.delegate = self
searchController = UISearchController(searchResultsController: suggestionController)
searchController.searchResultsUpdater = suggestionController
searchController.searchBar.isUserInteractionEnabled = false
searchController.searchBar.alpha = 0.5
}
override func viewDidLoad() {
super.viewDidLoad()
searchBar.addSubview(searchController.searchBar)
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.tintColor = .black
searchController.searchBar.isUserInteractionEnabled = true
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
hideHeader()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 10, *) {
visualEffectView.layer.cornerRadius = 9.0
visualEffectView.clipsToBounds = true
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func showHeader() {
changeHeader(height: 116.0)
}
func hideHeader() {
changeHeader(height: 0.0)
}
func changeHeader(height: CGFloat) {
tableView.beginUpdates()
if let headerView = tableView.tableHeaderView {
UIView.animate(withDuration: 0.25) {
var frame = headerView.frame
frame.size.height = height
self.tableView.tableHeaderView?.frame = frame
}
}
tableView.endUpdates()
}
func passData() {
guard let mapViewController = storyboard?.instantiateViewController(withIdentifier: "map") as? MapViewController else { return }
guard let mapItem = places?.first else { return }
guard let coordinate = mapItem.placemark.location?.coordinate else { return }
let span = MKCoordinateSpan(latitudeDelta: coordinate.latitude, longitudeDelta: coordinate.longitude)
let region = MKCoordinateRegion(center: coordinate, span: span)
mapViewController.boundingRegion = region
mapViewController.mapItems = [mapItem]
mapViewController.addAnnotationToMap()
}
private func search(for suggestedCompletion: MKLocalSearchCompletion) {
let searchRequest = MKLocalSearch.Request(completion: suggestedCompletion)
search(using: searchRequest)
}
private func search(for queryString: String?) {
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = queryString
search(using: searchRequest)
}
private func search(using searchRequest: MKLocalSearch.Request) {
if let region = boundingRegion {
searchRequest.region = region
}
UIApplication.shared.isNetworkActivityIndicatorVisible = true
localSearch = MKLocalSearch(request: searchRequest)
localSearch?.start { [weak self] (response, error) in
if error == nil {
self?.passData()
} else {
self?.displaySearchError(error)
return
}
self?.places = response?.mapItems
self?.boundingRegion = response?.boundingRegion
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
private func displaySearchError(_ error: Error?) {
if let error = error as NSError?, let errorString = error.userInfo[NSLocalizedDescriptionKey] as? String {
let alertController = UIAlertController(title: "Could not find any places.", message: errorString, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
}
}
Related
I am trying to build an IOS app where the user puts options in to a tableview, then the options appear and the spinning frame spins and stops at a random point.
I have put all the necessary code in ViewWillAppear but for some reason the app doesn't update with the user defaults, only when i close the app and re-open it.
It did actually work at one point but then stopped and i have no idea how to fix it. I am using TTFortuneWheel Pod.
ImageOne
ImageTwo
I will link my GitHub in case anyone wants to have a look at the full code.
https://github.com/jamesnjones/Just.Decide
below is the code for the main screen
import UIKit
import TTFortuneWheel
import AVFoundation
class ViewController: UIViewController, UINavigationControllerDelegate {
#IBOutlet weak var spinningWheel: TTFortuneWheel!
#IBOutlet weak var ResultsLabel: UILabel!
let transition = SlideInTransition()
var slices : [CarnivalWheel] = []
var result: String?
var player: AVAudioPlayer!
var soundIsOn = true
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
spinningWheel.initialDrawingOffset = 270.0
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("check")
slices = getSlices()
spinningWheel.slices = slices
spinningWheel.equalSlices = true
spinningWheel.frameStroke.width = 0
spinningWheel.titleRotation = CGFloat.pi
spinningWheel.slices.enumerated().forEach { (pair) in
let slice = pair.element as! CarnivalWheel
let offset = pair.offset
switch offset % 6 {
case 0: slice.style = .blue
case 1: slice.style = .green
case 2: slice.style = .grey
case 3: slice.style = .orange
case 4: slice.style = .purple
default: slice.style = .yellow
}
}
}
private func getSlices() -> [CarnivalWheel] {
PersistenceManager.retrieveSlices { [weak self] result in
guard let self = self else {return}
switch result {
case .success(let slices):
if slices.isEmpty {
print("this is where i will add an alert or sumin")
}else {
self.slices = slices
DispatchQueue.main.async {
self.reloadInputViews()
}
}
case .failure(let error):
print("Edit VC Errror ")
}
}
return slices
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
true
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
true
}
#IBAction func rotateButton(_ sender: UIButton) {
if soundIsOn {
playSound(soundName: "spinning")
ResultsLabel.text = ""
let randomNumber = Int.random(in: 0...slices.count - 1)
spinningWheel.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
self.spinningWheel.startAnimating(fininshIndex: randomNumber) { (finished) in
self.ResultsLabel.text = self.spinningWheel.slices[randomNumber].title
}
}
} else {
ResultsLabel.text = ""
let randomNumber = Int.random(in: 0...slices.count - 1)
spinningWheel.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
self.spinningWheel.startAnimating(fininshIndex: randomNumber) { (finished) in
self.ResultsLabel.text = self.spinningWheel.slices[randomNumber].title
}
}
}
}
Rather than putting it inside of your viewWillAppear put it inside of the viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
<#code#>
}
So I have a MapView which adds a pin to the current user location. Also I have two textfields which displays the coordinates of the pin. I want to add two features:
When I drag and drop the pin, I want to update the coordinates inside the textfields.
When I edit the coordinates in the textfield, it should move the pin to the updated coorinates from the textfields.
Here my code where i handle everything for MapView. Im still pretty new to coding, so the code could be confusing, sorry for that.
class LocateVC: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var finishedLocateButton: UIButton!
#IBOutlet weak var relocateButton: UIButton!
var coordinates: [[Double]]!
var latTitleLabel:[String]!
var latValueLabel:[String]!
var lngTitleLabel:[String]!
var lngValueLabel: [String]!
var isEdited = false
var customCallout: UIView?
//Important to track location
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.backgroundColor = Colors.grey.cgColor
//button titles
relocateButton.isHidden = true
relocateButton.setTitle("Relocate", for: .normal)
finishedLocateButton.setTitle("Continue", for: .normal)
navigationItem.title = "Location"
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
self.mapView.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
if isEdited == false {
if CLLocationManager.locationServicesEnabled() {
addPin()
}
}
// Latitude,Longitude
coordinates = [
[ProductData.shared.latitude!, ProductData.shared.longitude!],
]
} //end of viewDidLoad
override func viewWillAppear(_ animated: Bool) {
super.dismiss(animated: animated)
}
public func removePin() {
}
func dropPinFor(placemark: MKPlacemark) {
for annotation in mapView.annotations {
if annotation.isKind(of: MKPointAnnotation.self) {
// mapView.removeAnnotation(annotation) // removing the pins from the map
}
}
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
mapView.addAnnotation(annotation)
}
//1
public func addPin() {
if isEdited == false {
ProductData.shared.latitude = locationManager.location?.coordinate.latitude
ProductData.shared.longitude = locationManager.location?.coordinate.longitude
}
self.mapView.delegate = self
// adds an annotation to coordinates from productData
let point = ProductAnnotation(coordinate: CLLocationCoordinate2D(latitude: ProductData.shared.latitude! , longitude: ProductData.shared.longitude!))
self.mapView.addAnnotation(point)
// 3
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: ProductData.shared.latitude!, longitude: ProductData.shared.longitude!), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
self.mapView.setRegion(region, animated: true)
}
public func editCoord() {
performSegue(withIdentifier: "editCoord", sender: CustomCalloutView.self)
}
#IBAction func relocateButtonClicked(_ sender: Any) {
addPin()
}
#IBAction func finishedLocateButtonClicked(_ sender: Any) {
performSegue(withIdentifier: "finishedLocateSegue2", sender: self)
}
//4
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
if view.isKind(of: CustomCalloutView.self ) || view.isKind(of: AnnotationView.self) || view.isKind(of: ProductAnnotation.self) {
return
} else {
customCallout?.removeFromSuperview()
}
}
//3
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if view.annotation is MKUserLocation {
return
}
//this creates the callout
let views = Bundle.main.loadNibNamed("CustomCalloutView", owner: nil, options: nil)
let calloutView = views?[0] as! CustomCalloutView
calloutView.delegate = self
calloutView.lngTitleLabel.text = "Lng"
calloutView.latTitleLabel.text = "Lat"
calloutView.lngTextField.text = String(format:"%f", ProductData.shared.longitude!)
calloutView.latTextField.text = String(format:"%f", ProductData.shared.latitude!)
calloutView.latTextField.layer.borderWidth = 0.0
calloutView.lngTextField.layer.borderWidth = 0.0
calloutView.latTextField.isEnabled = false
calloutView.lngTextField.isEnabled = false
calloutView.latTextField.keyboardType = .numberPad
calloutView.lngTextField.keyboardType = .numberPad
calloutView.alpha = 1.0
calloutView.layer.cornerRadius = 8
calloutView.center = CGPoint(x: view.bounds.size.width / 2, y: -calloutView.bounds.size.height*0.52)
customCallout = calloutView
view.addSubview(calloutView)
mapView.setCenter((view.annotation?.coordinate)!, animated: true)
}
//2
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
var annotationView = self.mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
if annotationView == nil{
annotationView = AnnotationView(annotation: annotation, reuseIdentifier: "Pin")
annotationView?.isDraggable = true
annotationView?.canShowCallout = false
} else {
annotationView?.annotation = annotation
}
annotationView?.image = UIImage(named: "dot")
return annotationView
}
func saveButtonTapped() {
customCallout?.removeFromSuperview()
}
}
extension LocateVC: CustomCalloutViewDelegate {
func didClickEditButton() {
editCoord()
}
func didClickSaveButton() {
saveButtonTapped()
}
}
and here my custom callout:
class CustomCalloutView: UIView {
#IBOutlet weak var latTitleLabel: UILabel!
#IBOutlet weak var lngTitleLabel: UILabel!
#IBOutlet weak var editButton: UIButton!
#IBOutlet weak var saveButton: UIButton!
#IBOutlet weak var latTextField: UITextField!
#IBOutlet weak var lngTextField: UITextField!
var delegate: CustomCalloutViewDelegate?
var isEditing: Bool = false
#IBAction func didClickEditButton(_ sender: Any) {
// delegate?.didClickEditButton()
isEditing = true
latTextField.layer.borderWidth = 1.0
lngTextField.layer.borderWidth = 1.0
latTextField.isEnabled = true
lngTextField.isEnabled = true
saveButton.isHidden = false
}
#IBAction func saveButtonTapped(_ sender: Any) {
if isEditing == true {
if let lat = latTextField.text {
ProductData.shared.latitude = Double(lat)
}
if let lng = lngTextField.text {
ProductData.shared.longitude = Double(lng)
}
latTextField.layer.borderWidth = 0.0
lngTextField.layer.borderWidth = 0.0
latTextField.isEnabled = false
lngTextField.isEnabled = false
self.saveButton.setTitle("Save", for: .normal)
isEditing = false
} else {
self.saveButton.setTitle("Cancel", for: .normal)
delegate?.didClickSaveButton()
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if self.bounds.contains(point) {
return true
} else {
return self.subviews.contains { $0.frame.contains(point) }
}
}
}
protocol CustomCalloutViewDelegate {
func didClickEditButton()
func didClickSaveButton()
}
any idea how i can achive that? Maybe in a general way.
Thank you in advance
Here's a link to DSCenterPinMapView. A CocoaPod I recommend you to use.
It is a custom MapView with an animated and customizable center pin useful for selecting locations in map.
Here is also a guide on installing CocoaPods if it is new to you.
You should install the Pod an then, as a solution to your questions:
Implement the delegate so that you can get the location where pin drops.
pinMapView.delegate = self
extension MyViewController: DSCenterPinMapViewDelegate {
func didStartDragging() {
// My custom actions
}
func didEndDragging() {
// My custom actions
selectedLocation = pinMapView.mapview.centerCoordinate
// Update Text field
}
}
After you finish adding the coordinates on your TextFields you should call
pinMapView.center(on coordinate: myTextFieldCoordinate)
to center the map on the desired location.
After changing the marker position get coordinates like
let latitude = marker.coordinate.latitude
let longitude = marker.coordinate.longitude
And to set coordinate
let coordinate = CLLocationCoordinate2D(latitude: "your lat", longitude: "your long")
marker.coordinate = coordinate
mapView.setCenter(coordinate, animated: true)
if you not keeping the marker reference then
guard let marker = self.appleMap.annotations.first else {return}
let latitude = marker.coordinate.latitude
let longitude = marker.coordinate.longitude
And to set coordinate
guard let marker = self.appleMap.annotations.first else {return}
let coordinate = CLLocationCoordinate2D(latitude: "your lat", longitude: "your long")
marker.coordinate = coordinate
mapView.setCenter(coordinate, animated: true)
Hope that will help.
I can't figure out why the table view controller is not populating with placeMarks.name and coordinates. The user taps a pin on the map and adds the location to the model. Where then it sent to the table view controller, however, it is not displaying the data from the model. Below is my code.
import Foundation
import CoreLocation
class PlaceList : NSObject {
let locations: Places
var coordinate: CLLocationCoordinate2D { return locations.coordinate }
init(point: Places) {
self.locations = point
super.init()
}
var title: String? {
return locations.name
}
var subtitle: String? {
return "(\(locations.coordinate.latitude), \(locations.coordinate.longitude))"
}
}
import CoreLocation
import Foundation
class Places: NSObject {
var name: String
var coordinate: CLLocationCoordinate2D
init(name: String, coordinate: CLLocationCoordinate2D) {
self.name = name
self.coordinate = coordinate
}
}
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate,CLLocationManagerDelegate {
var placeModel: PlaceList?
var pointOfInterest: [Places] = []
var poi: [Places] = [] {
didSet {
pointOfInterest = poi
}
}
//Creating mapview object
var mapView: MKMapView!
var geoCoder = CLGeocoder()
//Initial cooridinate
var latitude = 43.136581
var longitude = -87.941101
var latitudeDelta = 0.3
var longitudeDelta = 0.3
//the object that determines the location
let placeManger = CLLocationManager()
//loading the map on the scene
override func loadView() {
super.loadView()
mapView = MKMapView()
view = mapView
placeManger.startUpdatingLocation()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
navigationItem.title = "Travel Wishlist"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.barTintColor = .blue
placeManger.delegate = self
placeManger.requestWhenInUseAuthorization()
mapView.delegate = self
navigationItem.rightBarButtonItem = rightBar
navigationItem.leftBarButtonItem = leftBar
mapView.addGestureRecognizer(tapGestrueRecongnizer)
centerMapInInitialCoordinates()
showPointsOfInterestInMap()
}
func showPointsOfInterestInMap() {
mapView.removeAnnotations(mapView.annotations)
for point in poi {
let pin = PlaceList(point: point)
mapView.addAnnotation(pin as! MKAnnotation)
}
}
func centerMapInInitialCoordinates() {
let span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: latitudeDelta, longitudeDelta: longitudeDelta)
let region:MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), span: span)
mapView.setRegion(region, animated: true)
}
var tapGestrueRecongnizer: UITapGestureRecognizer {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(loadPointOfInterests(recongnizer:)))
return tapGesture
}
var rightBar: UIBarButtonItem {
let rightBarButton =
UIBarButtonItem(title: "Find a place to add",
style: .plain, target: self,
action: #selector(rightbarButton))
rightBarButton.tintColor = .white
return rightBarButton
}
var leftBar: UIBarButtonItem {
let leftBarButton =
UIBarButtonItem(title:
"Look up", style: .plain,
target: self, action: #selector(leftButton))
leftBarButton.tintColor = .white
return leftBarButton
}
#objc func leftButton(leftButton: UIBarButtonItem) {
print("Left BarButton")
performSegue(withIdentifier: "place", sender: leftButton)
addPlace()
}
#objc func rightbarButton(right: UIBarButtonItem) {
addPlace()
}
func addPlace(){
// mapView.removeAnnotations(mapView.annotations)
for point in pointOfInterest {
let pin = PlaceList(point: point)
mapView.addAnnotation(pin as! MKAnnotation)
}
filterVisiblePOI()
}
func filterVisiblePOI() {
let visibleAnnotations = self.mapView.annotations(in: self.mapView.visibleMapRect)
var annotations = [PlaceList]()
for visibleAnnotation in visibleAnnotations {
if let annotation = visibleAnnotation as? PlaceList {
annotations.append(annotation)
}
}
}
#objc func loadPointOfInterests(recongnizer:UITapGestureRecognizer) {
let locationPoint = recongnizer.location(in: mapView)
let cooridinate = mapView.convert(locationPoint, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = cooridinate
mapView.addAnnotation(annotation)
geoCoder.reverseGeocodeLocation(CLLocation(latitude: cooridinate.latitude, longitude: cooridinate.longitude)) {(placeMarks : [CLPlacemark]?, error: Error?) in
if let placeLocation = placeMarks?[0] {
if error == nil && (placeMarks?.count)! > 0 {
let locationString = " \(placeLocation.name!)"
annotation.coordinate = cooridinate
annotation.title = locationString
print(locationString)
}
for point in self.pointOfInterest {
let pin = PlaceList(point: point)
self.mapView.addAnnotation(pin as! MKAnnotation)
self.poi.append(point)
print(point)
print(self.pointOfInterest)
print(pin)
}
}
}
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
self.navigationItem.rightBarButtonItem?.title = "Find a place to add"
}
private func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
//To Do
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "place" {
let nav = segue.destination as! UINavigationController
let detailViewControll = nav.topViewController as! VisitationTableViewController
for point in pointOfInterest {
let pin = PlaceList(point: point)
mapView.addAnnotation(pin as! MKAnnotation)
detailViewControll.poi = pointOfInterest
}
}
}
}
import Foundation
import UIKit
class VisitationTableViewController: UITableViewController {
var poi: [Places] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
let dissButton = UIBarButtonItem(title: "Done", style: .done
, target: self, action: #selector(dismisController))
self.navigationItem.rightBarButtonItem = dissButton
dissButton.tintColor = .white
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.title = "Visited"
navigationController?.navigationBar.barTintColor = .blue
print("view didload")
print(poi)
}
#objc func dismisController() {
presentingViewController?.dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return poi.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath)
let point = poi[indexPath.row]
cell.textLabel?.text = point.name
cell.detailTextLabel?.text = "(\(point.coordinate.latitude), \(point.coordinate.longitude))"
return cell
}
}
You may need
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
self.performSegue(withIdentifier:"place",sender:nil)
}
And make sure segue source is connected to the vc itself ( drag it from yellow roundec icon of mapVC inside IB )
When I ran the code, tapping into the map didn't add any object to pointOfInterest, which you are making a loop on loadPointOfInterests to add an object to poi but its empty, so make sure to add a Place object in poi
In VisitationTableViewController, make sure to add
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "locationCell")
}
I am displaying images in a collection view controller. When the cell is tapped, I am passing those images to page view controller, where the user is given an option to delete or add image description as you can see in the below images.
When the user clicks delete button, I would the page (or view controller) to be deleted (just like the behaviour seen, when delete button is clicked in in Apple iOS photos app).
I tried to achieve it, by passing an array of empty view controller to pageViewController (See Code A), which resulted in a error
The number of view controllers provided (0) doesn't match the number required (1) for the requested transition which makes sense.
Am I on the right track, if yes, how can I fix the issue ?
If not, Is there a better approach to achieve the same ?
Code A: Taken from Code B
pageVC.setViewControllers([], direction: .forward, animated: true, completion: nil)
Code B: Taken from UserPickedImageVC
func deleteCurrentImageObject(){
guard let controllers = self.navigationController?.viewControllers else{
return
}
for viewController in controllers {
if viewController.className == "UserPickedImagesVC"{
let vc = viewController as! UserPickedImagesVC
let objectCount = vc.imageObjectsArray.count
guard objectCount > 0 && objectCount >= itemIndex else {
return
}
vc.imageObjectsArray.remove(at: itemIndex) // Removing imageObject from the array
if let pageVC = vc.childViewControllers[0] as? UIPageViewController {
pageVC.setViewControllers([], direction: .forward, animated: true, completion: nil)
}
}
}
}
Storyboard
Here is the complete code (except some custom UICollectionViewCell):
UserPickedImagesCVC.swift
import UIKit
import ImagePicker
import Lightbox
private let imageCellId = "imageCell"
private let addCellId = "addImagesCell"
class UserPickedImagesCVC: UICollectionViewController, ImagePickerDelegate, UserPickedImagesVCProtocol {
let imagePickerController = ImagePickerController()
//var userPickedImages = [UIImage]()
var userPickedImages = [ImageObject]()
override func viewDidLoad() {
super.viewDidLoad()
imagePickerController.delegate = self as ImagePickerDelegate
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.collectionView!.register(ImageCVCell .self, forCellWithReuseIdentifier: imageCellId)
self.collectionView!.register(ImagePickerButtonCVCell.self, forCellWithReuseIdentifier: addCellId)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return userPickedImages.count + 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let item = indexPath.item
print("item: \(item)")
if item < userPickedImages.count {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: imageCellId, for: indexPath) as! ImageCVCell
let userPickedImageObject = userPickedImages[item]
//cell.showImagesButton.setImage(userPickedImage, for: .normal)
cell.showImagesButton.setImage(userPickedImageObject.image, for: .normal)
cell.showImagesButton.addTarget(self, action: #selector(showAlreadyPickedImages), for: .touchUpInside)
//cell.addButton.addTarget(self, action: #selector(showAlreadyPickedImages), for: .touchUpInside)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: addCellId, for: indexPath) as! ImagePickerButtonCVCell
cell.addButton.addTarget(self, action: #selector(showImagePickerController), for: .touchUpInside)
return cell
// Configure the cell
}
//Function shows imagePicker that helps in picking images and capturing images with camera
func showImagePickerController(){
print("showImagePickerController func called")
//self.present(imagePickerController, animated: true, completion: nil)
self.navigationController?.pushViewController(imagePickerController, animated: true)
}
func showAlreadyPickedImages(){
let vc = self.storyboard?.instantiateViewController(withIdentifier: "userPickedImagesVC") as! UserPickedImagesVC
//vc.contentImages = userPickedImages
vc.imageObjectsArray = userPickedImages
vc.showingAlreadySavedImages = true
self.navigationController?.pushViewController(vc, animated: true)
}
func setImagesInCells(imageObjects : [ImageObject]){
print("setImagesInCells func called in CVC")
userPickedImages += imageObjects
collectionView?.reloadData()
}
// MARK: - ImagePickerDelegate
func cancelButtonDidPress(_ imagePicker: ImagePickerController) {
imagePicker.dismiss(animated: true, completion: nil)
}
func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
guard images.count > 0 else { return }
let lightboxImages = images.map {
return LightboxImage(image: $0)
}
let lightbox = LightboxController(images: lightboxImages, startIndex: 0)
imagePicker.present(lightbox, animated: true, completion: nil)
}
func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
imagePicker.dismiss(animated: true, completion: nil)
let vc = storyboard?.instantiateViewController(withIdentifier: "userPickedImagesVC") as! UserPickedImagesVC
//vc.contentImages = images
vc.imageObjectsArray = convertImagesToImageObjects(images)
//self.present(vc, animated: true, completion: nil)
self.navigationController?.pushViewController(vc, animated: true)
}
func convertImagesToImageObjects(_ imagesArray : [UIImage]) -> [ImageObject]{
var imageObjects = [ImageObject]()
for image in imagesArray{
var imageObject = ImageObject()
imageObject.image = image
imageObject.imageDescription = ""
imageObjects.append(imageObject)
}
return imageObjects
}
}
UserPickedImagesVC.swift
import UIKit
protocol UserPickedImagesVCProtocol{
func setImagesInCells(imageObjects : [ImageObject])
}
class ImageObject : NSObject{
var imageDescription : String?
var image : UIImage?
}
class UserPickedImagesVC: UIViewController, UIPageViewControllerDataSource {
var pageViewController : UIPageViewController?
let placeholderText = "Image description.."
var imageObjectsArray = [ImageObject]()
var delegate : UserPickedImagesVCProtocol!
var showingAlreadySavedImages = false
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = [] // To avoid view going below nav bar
//self.delegate = self.navigationController?.viewControllers
// Do any additional setup after loading the view, typically from a nib.
if showingAlreadySavedImages{
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(doneTapped))
}else{
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveTapped))
}
// createImageAndDescriptionDict()
createPageViewController()
setupPageControl()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createPageViewController(){
print("createPageViewController func called")
let pageController = self.storyboard?.instantiateViewController(withIdentifier: "userPickedImagesPageController") as! UIPageViewController
pageController.dataSource = self
if imageObjectsArray.count > 0 {
let firstController = getItemController(0)
let startingViewControllers = [firstController]
pageController.setViewControllers(startingViewControllers as! [UIViewController], direction: .forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview((pageViewController?.view)!)
pageViewController?.didMove(toParentViewController: self)
}
// Creata the appearance of pagecontrol
func setupPageControl(){
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.gray
appearance.currentPageIndicatorTintColor = UIColor.white
appearance.backgroundColor = UIColor.darkGray
}
//MARK: Delagate methods
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! UserPickedImageVC
if itemController.itemIndex > 0 {
return self.getItemController(itemController.itemIndex-1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! UserPickedImageVC
if itemController.itemIndex + 1 < imageObjectsArray.count{
return getItemController(itemController.itemIndex+1)
}
return nil
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return imageObjectsArray.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
func currentControllerIndex() -> Int{
let pageItemController = self.currentControllerIndex()
if let controller = pageItemController as? UserPickedImageVC{
return controller.itemIndex
}
return -1
}
func currentController() -> UIViewController?{
if(self.pageViewController?.viewControllers?.count)! > 0{
return self.pageViewController?.viewControllers?[0]
}
return nil
}
func getItemController(_ itemIndex:Int) -> UserPickedImageVC?{
if itemIndex < imageObjectsArray.count{
let pageItemController = self.storyboard?.instantiateViewController(withIdentifier: "userPickedImageVC") as! UserPickedImageVC
pageItemController.itemIndex = itemIndex
//pageItemController.imageName = imageObjectsArray[itemIndex]
//pageItemController.imageToShow = imageObjectsArray[itemIndex]
//pageItemController.imageToShow = getImageFromImageDescriptionArray(itemIndex, imagesAndDescriptionArray)
pageItemController.imageObject = imageObjectsArray[itemIndex]
pageItemController.itemIndex = itemIndex
pageItemController.showingAlreadySavedImage = showingAlreadySavedImages
print("Image Name from VC: \(imageObjectsArray[itemIndex])")
return pageItemController
}
return nil
}
// Passing images back to Collection View Controller when save button is tapped
func saveTapped(){
let viewControllers = self.navigationController?.viewControllers
//print("viewControllers: \(viewControllers)")
if let destinationVC = viewControllers?[0]{
self.delegate = destinationVC as! UserPickedImagesVCProtocol
//self.delegate.setImagesInCells(images : imageObjectsArray)
self.delegate.setImagesInCells(imageObjects : imageObjectsArray)
self.navigationController?.popToViewController(destinationVC, animated: true)
}
}
func doneTapped(){
let viewControllers = self.navigationController?.viewControllers
if let destinationVC = viewControllers?[0] {
self.navigationController?.popToViewController(destinationVC, animated: true)
}
}
}
UserPickedImageVC.swift
import UIKit
import ImageScrollView
extension UIViewController {
var className: String {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last!;
}
}
class UserPickedImageVC: UIViewController, UITextViewDelegate {
var itemIndex : Int = 0
var imageDescription : String = ""
var imageScrollView = ImageScrollView()
var imageDescriptionTextView : UITextView!
var imageToShow : UIImage!
var imageObject : ImageObject?
var deleteButton = UIButton(type: .system)
var showingAlreadySavedImage = false
var pageViewController : UIPageViewController!
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = [] // To avoid images going below the navigation bars
pageViewController = self.parent as! UIPageViewController
setConstraints()
setImageAndDescription()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: TextView delegate methods
func textViewDidBeginEditing(_ textView: UITextView)
{
if (imageDescriptionTextView.text == "Image description..")
{
imageDescriptionTextView.text = ""
imageDescriptionTextView.textColor = .black
}
imageDescriptionTextView.becomeFirstResponder() //Optional
}
func textViewDidEndEditing(_ textView: UITextView)
{
let imageDescription = imageDescriptionTextView.text
if (imageDescription == "")
{
imageDescriptionTextView.text = "Image description.."
imageDescriptionTextView.textColor = .lightGray
}
imageObject?.imageDescription = imageDescription
updateImageObject(imageObject!)
imageDescriptionTextView.resignFirstResponder()
}
//MARK: Private Methods
func setImageAndDescription(){
if let imageToDisplay = imageObject?.image{
imageScrollView.display(image: imageToDisplay) // Setting Image
}
imageDescriptionTextView.text = imageObject?.imageDescription // Setting Description
}
// Function to update imageObject in UserPickedImagesVC
func updateImageObject(_ imageObject: ImageObject){
guard let controllers = self.navigationController?.viewControllers else{
return
}
for viewController in controllers {
if viewController.className == "UserPickedImagesVC" {
let vc = viewController as! UserPickedImagesVC
vc.imageObjectsArray[itemIndex] = imageObject
}
}
}
// Function to delete imageObject from UserPickedImagesVC
func deleteCurrentImageObject(){
guard let controllers = self.navigationController?.viewControllers else{
return
}
for viewController in controllers {
if viewController.className == "UserPickedImagesVC"{
let vc = viewController as! UserPickedImagesVC
let objectCount = vc.imageObjectsArray.count
guard objectCount > 0 && objectCount >= itemIndex else {
return
}
vc.imageObjectsArray.remove(at: itemIndex) // Removing imageObject from the array
if let pageVC = vc.childViewControllers[0] as? UIPageViewController {
pageVC.setViewControllers([], direction: .forward, animated: true, completion: nil)
}
}
}
}
func showOrHideDeleteButton(){
if showingAlreadySavedImage{
print("deleteButton.isNotHidden")
deleteButton.isHidden = false
}else{
print("deleteButton.isHidden")
deleteButton.isHidden = true
}
}
func setConstraints(){
let viewSize = self.view.frame.size
let viewWidth = viewSize.width
let viewHeight = viewSize.height
print("viewWidth: \(viewWidth), viewHeight: \(viewHeight)")
view.addSubview(imageScrollView)
imageScrollView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
deleteButton.tintColor = Colors.iOSBlue
deleteButton.setImage(#imageLiteral(resourceName: "delete"), for: .normal)
deleteButton.backgroundColor = Colors.white
deleteButton.layer.cornerRadius = 25
deleteButton.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
//deleteButton.currentImage.
deleteButton.imageView?.tintColor = Colors.iOSBlue
deleteButton.addTarget(self, action: #selector(deleteCurrentImageObject), for: .touchUpInside)
deleteButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(deleteButton)
showOrHideDeleteButton()
imageDescriptionTextView = UITextView()
imageDescriptionTextView.delegate = self as! UITextViewDelegate
imageDescriptionTextView.text = "Image description.."
imageDescriptionTextView.textColor = .lightGray
//imageScrollView.clipsToBounds = true
imageDescriptionTextView.translatesAutoresizingMaskIntoConstraints = false
//imageDescriptionTextView.backgroundColor = UIColor.white.withAlphaComponent(0.8)
imageDescriptionTextView.backgroundColor = UIColor.white
imageDescriptionTextView.layer.cornerRadius = 5
imageDescriptionTextView.layer.borderColor = UIColor.lightGray.cgColor
imageDescriptionTextView.layer.borderWidth = 0.5
view.addSubview(imageDescriptionTextView)
let viewsDict = [
"imageDescriptionTextView" : imageDescriptionTextView,
"deleteButton" : deleteButton
] as [String:Any]
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-5-[imageDescriptionTextView]-70-|", options: [], metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[imageDescriptionTextView(50)]-5-|", options: [], metrics: nil, views: viewsDict))
imageDescriptionTextView.sizeThatFits(CGSize(width: imageDescriptionTextView.frame.size.width, height: imageDescriptionTextView.frame.size.height))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[deleteButton(50)]-5-|", options: [], metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[deleteButton(50)]-5-|", options: [], metrics: nil, views: viewsDict))
}
}
create a call back property in UserPickedImageVC.swift
typealias DeleteCallBack = (int) -> Void
...
var itemIndex : Int = 0
var deleteCallBack:DeleteCallBack?
...
func deleteCurrentImageObject(){
self.deleteCallBack?(self.itemIndex)
}
in UserPickedImagesVC.swift
func getItemController(_ itemIndex:Int) -> UserPickedImageVC?{
if itemIndex < imageObjectsArray.count{
let pageItemController = self.storyboard?.instantiateViewController(withIdentifier: "userPickedImageVC") as! UserPickedImageVC
pageItemController.itemIndex = itemIndex
pageItemController.imageObject = imageObjectsArray[itemIndex]
pageItemController.itemIndex = itemIndex
pageItemController.showingAlreadySavedImage = showingAlreadySavedImages
print("Image Name from VC: \(imageObjectsArray[itemIndex])")
pageItemController.deleteCallBack = {
[weak self] (index) -> Void in
self?.deleteItemAt(index: index)
}
return pageItemController
}
return nil
}
func deleteItemAt(index: Int) {
if (imageObjectsArray.count > 1) {
imageObjectsArray.remove(at: itemIndex)
self.pageViewController.dataSource = nil;
self.pageViewController.dataSource = self;
let firstController = getItemController(0)
let startingViewControllers = [firstController]
pageViewController.setViewControllers(startingViewControllers as! [UIViewController], direction: .forward, animated: false, completion: nil)
} else {
//redirect here
_ = navigationController?.popViewController(animated: true)
}
}
Evening, I have built a search controller, and I have also the code to programmatically create his search bar. But I would like to replace this code with a search bar designed in the story board.
So my question is, how can I connect the outlet to the search controller?
this is my code:
public class CustomSearchController: UISearchController {
public var customSearchBar = UISearchBar()
override public var searchBar: UISearchBar {
get {
return self.customSearchBar
}
}
}
func configureSearchController() {
searchController = CustomSearchController()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.customSearchBar = self.customSearchBar
searchController.searchBar.delegate = self
self.definesPresentationContext = true
}
extension EarthSearcherTableViewController : UISearchResultsUpdating {
public func updateSearchResults(for searchController: UISearchController) {
//Code
guard let text = searchController.searchBar.text else { return }
self.getLocations(forSearchString: text)
}
fileprivate func getLocations(forSearchString searchString: String) {
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchString
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else { return }
self.locations = response.mapItems
self.tableView.reloadData()
}
}
#objc func zoomToCurrentLocation() {
//Clear existing pins
mapView.removeAnnotations(mapView.annotations)
mapView.removeOverlays(mapView.overlays)
let annotation = MKPointAnnotation()
annotation.coordinate = mapView.userLocation.coordinate
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.005, 0.005)
let region = MKCoordinateRegionMake(mapView.userLocation.coordinate, span)
mapView.setRegion(region, animated: true)
let location = CLLocation(latitude: mapView.userLocation.coordinate.latitude, longitude: mapView.userLocation.coordinate.longitude)
mapView.add(MKCircle(center: location.coordinate, radius: 50))
}
}
I think I have a problem with the delegates, because when I type in the search bar, the results does not show off in the table
Any tips?
Subclass UISearchController and override searchBar getter to return the searchBar that you want.
public class mySearchController: UISearchController {
public var customSearchBar = UISearchBar()
override public var searchBar: UISearchBar {
get {
return customSearchBar
}
}
}
In your method, set the customSearchBar to your searchBar.
func configureSearchController() {
searchController = mySearchController()
searchController.customSearchBar = self.searchBar
//Other stuff...
}