I'm trying to create two tableViews in one UIViewController. But when I'm trying to assign value to UILabel, getting an error: fatal error: unexpectedly found nil while unwrapping an Optional value
I wonder why, I have almost the same code for TableViewController with one tableView and it works with no issues. It looks like these UI Labels are not initialized when trying to assign value to it. But don't understand how to fix it.
It fails here:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if tableView == self.guestsTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "guestCell", for: indexPath) as! GuestAtTableTableViewCell
if let guestsTable = guestsTableFetchedResultsController?.object(at: indexPath) {
print(guestsTable.guestName) // works fine, prints the value
print(cell.guestNameLabel.text) //fails here with error fatal error: unexpectedly found nil while unwrapping an Optional value
cell.guestNameLabel.text = guestsTable.guestName
cell.openTimeLabel.text = String(describing: guestsTable.openTime)
cell.cellDelegate = self
}
}
else if tableView == self.ordersTableView {
cell = tableView.dequeueReusableCell(withIdentifier: "orderCell", for: indexPath)
//to be done
}
// Configure the cell...
return cell!
}
Full code of this class:
import UIKit
import CoreData
class TableUIViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CellWithButtonDelegate {
//The following two variables will not be nil because prepare for segue will set them
var tableName: String?
var table: TablesTable? = nil
fileprivate var currentTableSession: TableSessionTable? {
get {
let tableSessionTable = TableSessionTable()
return tableSessionTable.getCurrentTableSession(table: table!)
}
}
fileprivate var guestsTableFetchedResultsController: NSFetchedResultsController<GuestsTable>?
fileprivate var ordersTableFetchedResultsController: NSFetchedResultsController<OrdersTable>?
#IBOutlet weak var tableNameLabel: UILabel!
#IBOutlet weak var tableCapacityLabel: UILabel!
#IBOutlet weak var tableCountOfGuestsLabel: UILabel!
#IBOutlet weak var tableDescriptionTextView: UITextView!
#IBAction func closeTableButtonPressed(_ sender: UIButton) {
}
#IBOutlet weak var guestsTableView: UITableView!
#IBOutlet weak var ordersTableView: UITableView!
#IBAction func addGuestButtonPressed(_ sender: UIButton) {
let guestsTable = GuestsTable()
let tablesTable = TablesTable()
let table = Table(tableName: tableName!, tableCapacity: 0, locationX: nil, locationY: nil, tableImage: nil)
try? guestsTable.addNewGuest(table: tablesTable.getOrCreateTable(table: table))
updateUI()
}
#IBAction func addOrderButtonPressed(_ sender: UIButton) {
}
override func viewDidLoad() {
guestsTableView.dataSource = self
guestsTableView.delegate = self
guestsTableView.register(GuestAtTableTableViewCell.self, forCellReuseIdentifier: "guestCell")
ordersTableView.dataSource = self
ordersTableView.delegate = self
ordersTableView.register(UITableViewCell.self, forCellReuseIdentifier: "orderCell")
updateUI()
}
func didPressButton(table: TablesTable) {
}
private func updateUI () {
let tableView = guestsTableView
let context = AppDelegate.viewContext
let request : NSFetchRequest<GuestsTable> = GuestsTable.fetchRequest()
request.predicate = NSPredicate(format: "table= %#", currentTableSession!)
request.sortDescriptors = [NSSortDescriptor(key: "guestName", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
guestsTableFetchedResultsController = NSFetchedResultsController<GuestsTable>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
try? guestsTableFetchedResultsController?.performFetch()
tableView?.reloadData()
}
private func updateUI1 () {
let tableView = ordersTableView
let context = AppDelegate.viewContext
let request : NSFetchRequest<OrdersTable> = OrdersTable.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "menuItem", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
ordersTableFetchedResultsController = NSFetchedResultsController<OrdersTable>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
try? ordersTableFetchedResultsController?.performFetch()
tableView?.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if tableView == self.guestsTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "guestCell", for: indexPath) as! GuestAtTableTableViewCell
if let guestsTable = guestsTableFetchedResultsController?.object(at: indexPath) {
print(guestsTable.guestName) // works fine, prints the value
print(cell.guestNameLabel.text) //fails here with error fatal error: unexpectedly found nil while unwrapping an Optional value
cell.guestNameLabel.text = guestsTable.guestName
cell.openTimeLabel.text = String(describing: guestsTable.openTime)
cell.cellDelegate = self
}
}
else if tableView == self.ordersTableView {
cell = tableView.dequeueReusableCell(withIdentifier: "orderCell", for: indexPath)
//to be done
}
// Configure the cell...
return cell!
}
func numberOfSections(in tableView: UITableView) -> Int {
if tableView == self.guestsTableView {
return guestsTableFetchedResultsController?.sections?.count ?? 1
}
else if tableView == self.ordersTableView {
return ordersTableFetchedResultsController?.sections?.count ?? 1
}
else {return 1}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.guestsTableView {
if let sections = guestsTableFetchedResultsController?.sections, sections.count > 0 {
return sections[section].numberOfObjects
}
else {
return 0
}
}
else if tableView == self.ordersTableView {
if let sections = ordersTableFetchedResultsController?.sections, sections.count > 0 {
return sections[section].numberOfObjects
}
else {
return 0
}
}
else {return 0}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if tableView == self.guestsTableView {
if let sections = guestsTableFetchedResultsController?.sections, sections.count > 0 {
return sections[section].name
}
else {
return nil
}
}
else if tableView == self.ordersTableView {
if let sections = ordersTableFetchedResultsController?.sections, sections.count > 0 {
return sections[section].name
}
else {
return nil
}
}
else {return nil}
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
if tableView == guestsTableView {
return guestsTableFetchedResultsController?.sectionIndexTitles
}
else {
return ordersTableFetchedResultsController?.sectionIndexTitles
}
}
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
if tableView == guestsTableView {
return guestsTableFetchedResultsController?.section(forSectionIndexTitle: title, at: index) ?? 0
}
else if tableView == ordersTableView {
return ordersTableFetchedResultsController?.section(forSectionIndexTitle: title, at: index) ?? 0
}
else {return 0}
}
}
And full code of UITableViewCell class:
import UIKit
class GuestAtTableTableViewCell: UITableViewCell {
weak var cellDelegate: CellWithButtonDelegate?
#IBOutlet weak var guestNameLabel: UILabel!
#IBOutlet weak var openTimeLabel: UILabel!
#IBAction func didPressButton(_ sender: UIButton) {
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I guess you have a xib for your UITableViewCell register the xib instead of the class.
Use the following:
guestsTableView.register(UINib.init(nibName: "GuestAtTableTableViewCell", bundle: nil), forCellReuseIdentifier: "guestCell")
As you have created a prototype cell in the storyboard itself you should select the cell in the storyboard and set its identifier there. Next remove the register line from your code for guestCell. It should work
Related
I'm working on creating a 'Rate' feature in my app where it pulls the users to rate from an external API. I then create a TableView which adds cells that contain a section name (the name of the User) and a UISlider.
What I'm trying to do is gather the data from the UISliders along with the User ID associated with the User who's section it is so that I can call a POST action to the API to send the rating data too.
Here's the code I have: (For the record, first time with Swift, the comments are me trying to figure out how to do this). Any ideas?
import UIKit
import Alamofire
class RateViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var submitButton: UIButton!
#IBOutlet weak var activeSurveyLabel: UILabel!
private var myTableView: UITableView!
var usersToRate = [User]()
var activeSurveyId: String?
var rateData = [String: Int]()
var sectionIdToUserId = [Int: String]()
var userIdSection = [Int: String]()
override func viewDidLoad() {
super.viewDidLoad()
submitButton.isHidden = true
submitButton.addTarget(self, action: #selector(pressSubmitButton(button:)), for: UIControlEvents.touchUpInside)
activeSurveyLabel.isHidden = false
self.loadUsersToRate()
Alamofire.request(RateRouter.getSurvey()).responseJSON { response in
debugPrint(response)
if let string = response.result.value as? [[String: Any]]{
if string.count == 0 {
return
}
self.submitButton.isHidden = false
self.activeSurveyLabel.isHidden = true
for survey in string {
if let survey = Survey(json: survey) {
self.activeSurveyId = survey._id!
print(survey._id!)
}
}
//if there's a result, show slider bars for each person on the team with rating scales (except for the person logged in)
//have a submit button with a post request for the votes
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = barHeight * CGFloat(self.usersToRate.count * 5)
self.myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
self.myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
self.myTableView.dataSource = self
self.myTableView.delegate = self
self.view.addSubview(self.myTableView)
} else {
print(response.result.error!)
let label = UILabel()
label.center = self.view.center
label.text = "No Active Surveys"
self.view.addSubview(label)
}
}
// Do any additional setup after loading the view.
}
func loadUsersToRate() {
Alamofire.request(RateRouter.getUsers()).responseJSON { response in
guard let jsonArray = response.result.value as? [[String: Any]] else {
print("didn't get array, yo")
return
}
for item in jsonArray {
if let user = User(json: item) {
self.usersToRate.append(user)
self.rateData.updateValue(5, forKey: user.userId!) //initialize user average data at 5 in case they don't change it
print (self.rateData)
}
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Num: \(indexPath.row)")
print("Value: \(usersToRate[indexPath.row])")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
self.sectionIdToUserId.updateValue(usersToRate[section].userId!, forKey: section)
print(self.sectionIdToUserId)
return "\(usersToRate[section].name!)"
}
func numberOfSections(in tableView: UITableView) -> Int {
return usersToRate.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
let slider = UISlider(frame: CGRect(x: 0, y:0, width: 400, height: 44))
slider.isUserInteractionEnabled = true
slider.minimumValue = 1
slider.maximumValue = 10
slider.value = 5
slider.tag = indexPath.section
slider.addTarget(self, action: #selector(sliderValueChange(sender:)), for: UIControlEvents.valueChanged)
cell.addSubview(slider)
return cell
}
func sliderValueChange(sender: UISlider) {
//get slider value
var currentValue = Int(sender.value)
print(currentValue)
//self.rateData.updateValue(currentValue, forKey: self.sectionIdToUserId.values[sender.])
//get user id for that value
//self.rateData.updateValue(currentValue, rateData[section])
// self.rateData.updateValue(currentValue, forKey: 0)
}
func pressSubmitButton(button: UIButton) {
for user in usersToRate {
// Alamofire.request(RateRouter.vote(voteFor: user.userId!, survey: activeSurveyId, rating:))
}
}
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.
}
*/
}
To answer my own question, what I did was create a different model called 'Votes' that stored the information for each vote.
Then to fill that array I created a tag for the slider in the tableView based on the section ID and used that to update the current value.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
let slider = UISlider(frame: CGRect(x: 0, y:0, width: cell.frame.width , height: 44))
slider.tag = indexPath.section
slider.addTarget(self, action: #selector(sliderValueChange(sender:)), for: UIControlEvents.valueChanged)
cell.addSubview(slider)
return cell
}
func sliderValueChange(sender: UISlider) {
//get slider value
var currentValue = Int(sender.value)
print(currentValue)
voteData[sender.tag].rating = currentValue
}
I know that this question has answered in this site But I tried the code and receive an Error please help me to show my camera roll in a collection view (I know that I have to add photo usage in info.plist please Just focus on my codes thanks!!!)
here is my view controller code
class translateViewController: UIViewController , UINavigationControllerDelegate , UIImagePickerControllerDelegate , UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var myimageView: UIImageView!
#IBAction func importImage(_ sender: Any) {
let image = UIImagePickerController()
image.delegate = self
image.sourceType = UIImagePickerControllerSourceType.photoLibrary
image.allowsEditing = false
self.present(image , animated: true)
{
}
}
#IBOutlet weak var cameraRollCollectionView: UICollectionView!
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage{
myimageView.image = image
}
else {
print("Error")
}
self.dismiss(animated: true, completion: nil)
}
#IBOutlet var translatebackgroundimg: UIImageView!
#IBOutlet var translatefrontimg: UIImageView!
var assetCollection: PHAssetCollection!
var photosAsset: PHFetchResult<AnyObject>!
var assetThumbnailSize: CGSize!
override func viewDidLoad() {
super.viewDidLoad()
let fetchOptions = PHFetchOptions()
let collection:PHFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let first_Obj:AnyObject = collection.firstObject{
//found the album
self.assetCollection = first_Obj as! PHAssetCollection
}
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = CGRect(x: self.translatebackgroundimg.frame.origin.x, y: self.translatebackgroundimg.frame.origin.y, width: self.translatebackgroundimg.frame.size.width, height: self.translatebackgroundimg.frame.size.height)
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.translatebackgroundimg.addSubview(blurView)
// Do any additional setup after loading the view.
translatefrontimg.image = UIImage(named: "Translate.png")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.dark)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = translatebackgroundimg.bounds
translatebackgroundimg.addSubview(blurView)
translatebackgroundimg.frame = self.view.bounds
}
override func viewWillAppear(_ animated: Bool) {
// Get size of the collectionView cell for thumbnail image
if let layout = self.cameraRollCollectionView!.collectionViewLayout as? UICollectionViewFlowLayout{
let cellSize = layout.itemSize
self.assetThumbnailSize = CGSize(width: cellSize.width, height: cellSize.height)
}
//fetch the photos from collection
self.photosAsset = (PHAsset.fetchAssets(in: self.assetCollection, options: nil) as AnyObject!) as! PHFetchResult<AnyObject>!
self.cameraRollCollectionView!.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
var count: Int = 0
if(self.photosAsset != nil){
count = self.photosAsset.count
}
return count;
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cameraCell", for: indexPath as IndexPath)
//Modify the cell
let asset: PHAsset = self.photosAsset[indexPath.item] as! PHAsset
PHImageManager.default().requestImage(for: asset, targetSize: self.assetThumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: {(result, info)in
if result != nil {
cameraCell.userImage.image = result
}
})
return cell
}
// MARK: - UICollectionViewDelegateFlowLayout methods
func collectionView(collectinView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 4
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 1
}
// UIImagePickerControllerDelegate Methods
func imagePickerControllerDidCancel(_ picker: UIImagePickerController){
picker.dismiss(animated: true, completion: nil)
}
and here is my CollectionViewCell codes
class cameraCell: UICollectionViewCell , UIImagePickerControllerDelegate {
#IBOutlet weak var userImage: UIImageView!
func configurecell(image: UIImage){
userImage.image = image
}
}
I've got a simple to do list app, and I would like the items in the list to appear after the app is closed. What I have so far does not do it, not sure if everything is in the right place?
Code in my first ViewController:
var list = [String]()
class FirstViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var myTableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.text = list[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
list.remove(at: indexPath.row)
myTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
myTableView.reloadData()
if let x = UserDefaults.standard.object(forKey: "cell") as? [String]! {
list = x
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Code in my second ViewController:
class SecondViewController: UIViewController {
#IBOutlet weak var input: UITextField!
#IBAction func addItemButton(_ sender: AnyObject) {
list.append(input.text!)
input.text = ""
UserDefaults.standard.set(input.text, forKey: "cell")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
You are reloading the tableview at viewDidAppear before getting the data. Get your data from user defaults first (set list) and then reload the table view.
Hint:-
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "cell") as? [String]! {
list = x
myTableView.reloadData()
}
}
i hope all is well
i have an issue with cell in table view it's not releasing when i scrolling in table view the following is my code
class MovieDownloadedTableViewCell: UITableViewCell {
#IBOutlet weak var movieImageView: UIImageView!
#IBOutlet weak var movieNameLabel: UILabel!
#IBOutlet weak var movieEmptyCircleImageView: UIImageView!
#IBOutlet weak var movieCheckMarkImageView: UIImageView!
var fetchURL:URL? {
didSet {
updateUI()
}
}
func updateUI() {
movieImageView.image = nil
movieNameLabel.text = nil
movieEmptyCircleImageView.isHidden = true
movieCheckMarkImageView.isHidden = true
if let fetchURL = fetchURL {
if fetchURL.pathExtension == "" {
movieImageView.image = UIImage(named: "folder")
}else{
movieImageView.image = UIImage(named: "movie")
}
movieNameLabel.text = fetchURL.lastPathComponent
}// end the if let
}// end the func updateUI
}// end the class
the above my custom table view cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MovieDownloadedTableViewCell
// defining the cell
if let cell = cell {
cell.tintColor = UIColor.white
cell.fetchURL = operationDocumentDirectoryObject.arrayOfMovieURL?[indexPath.row]
if isShowToolBar {
if operationDocumentDirectoryObject?.arrayOfShowAllEmptyCircle?[indexPath.row] == indexPath.row {
cell.movieEmptyCircleImageView.isHidden = false
}// end the if
if operationDocumentDirectoryObject.dictionaryHoldIndexCellForDisplayWhichCellSelected.count > 0 {
if operationDocumentDirectoryObject.dictionaryHoldIndexCellForDisplayWhichCellSelected[indexPath.row] == indexPath.row {
cell.movieEmptyCircleImageView.isHidden = true
cell.movieCheckMarkImageView.isHidden = false
}
}
}// end if for the isShowVar
}// end the creating cell
return cell!
}
please where my wrong code how can i make my table view cell when i scrolling is releasing from memory every time
thank you very much
I am having difficulties converting two sections of code from Swift 2 to Swift 3
The working Swift 2 Code Block was
func showRoute(routes: [MKRoute], time: NSTimeInterval) {
var directionsArray = [(startingAddress: String, endingAddress: String, route: MKRoute)]()
for i in 0..<routes.count {
plotPolyline(routes[i])
directionsArray += [(locationArray[i].textField.text!,
locationArray[i+1].textField.text!, routes[i])]
}
displayDirections(directionsArray)
printTimeToLabel(time)
}
Swift 3 has converted this to
func showRoute(routes: [MKRoute], time: TimeInterval) {
var directionsArray = [(startingAddress: String, endingAddress: String, route: MKRoute)]()
for i in 0..<routes.count {
plotPolyline(route: routes[i])
directionsArray += [(locationArray[i].textField?.text,
locationArray[i+1].textField?.text, routes[i])]
}
displayDirections(directionsArray: directionsArray)
printTimeToLabel(time: time)
}
This produces an error on the line
directionsArray += [(locationArray[i].textField?.text,
locationArray[i+1].textField?.text, routes[i])]
Cannot convert value of type '[(startingAddress: String, endingAddress: String, route: MKRoute)]' to expected argument type 'inout _'
If anyone can help i would really appreciate it
Just need
directionsArray += [(startingAddress : locationArray[i].textField!.text!,
endingAddress : locationArray[i+1].textField!.text!,
route : routes[i])]
I saw your question, but There are no answers. And, That is not only one problem.
Here is full body of ViewController:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController {
#IBOutlet weak var sourceField: UITextField!
#IBOutlet weak var destinationField1: UITextField!
#IBOutlet weak var destinationField2: UITextField!
#IBOutlet weak var topMarginConstraint: NSLayoutConstraint!
#IBOutlet var enterButtonArray: [UIButton]!
var originalTopMargin: CGFloat!
let locationManager = CLLocationManager()
var locationTuples: [(textField: UITextField?, mapItem: MKMapItem?)]!
var locationsArray: [(textField: UITextField?, mapItem: MKMapItem?)] {
var filtered = locationTuples.filter({ $0.mapItem != nil })
filtered += [filtered.first!]
return filtered
}
override func viewDidLoad() {
super.viewDidLoad()
originalTopMargin = topMarginConstraint.constant
locationTuples = [(sourceField, nil), (destinationField1, nil), (destinationField2, nil)]
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestLocation()
}
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.isNavigationBarHidden = true
}
#IBAction func getDirections(_ sender: AnyObject) {
view.endEditing(true)
performSegue(withIdentifier: "show_directions", sender: self)
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if locationTuples[0].mapItem == nil ||
(locationTuples[1].mapItem == nil && locationTuples[2].mapItem == nil) {
showAlert("Please enter a valid starting point and at least one destination.")
return false
} else {
return true
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let directionsViewController = segue.destination as! DirectionsViewController
directionsViewController.locationArray = locationsArray
}
#IBAction func addressEntered(_ sender: UIButton) {
view.endEditing(true)
let currentTextField = locationTuples[sender.tag-1].textField
CLGeocoder().geocodeAddressString(currentTextField!.text!,
completionHandler: {(placemarks, error) -> Void in
if let placemarks = placemarks {
var addresses = [String]()
for placemark in placemarks {
addresses.append(self.formatAddressFromPlacemark(placemark))
}
self.showAddressTable(addresses, textField:currentTextField!,
placemarks:placemarks, sender:sender)
} else {
self.showAlert("Address not found.")
}
} )
}
func showAddressTable(_ addresses: [String], textField: UITextField,
placemarks: [CLPlacemark], sender: UIButton) {
let addressTableView = AddressTableView(frame: UIScreen.main.bounds, style: UITableViewStyle.plain)
addressTableView.addresses = addresses
addressTableView.currentTextField = textField
addressTableView.placemarkArray = placemarks
addressTableView.mainViewController = self
addressTableView.sender = sender
addressTableView.delegate = addressTableView
addressTableView.dataSource = addressTableView
view.addSubview(addressTableView)
}
func formatAddressFromPlacemark(_ placemark: CLPlacemark) -> String {
return (placemark.addressDictionary!["FormattedAddressLines"] as! [String]).joined(separator: ", ")
}
#IBAction func swapFields(_ sender: AnyObject) {
swap(&destinationField1.text, &destinationField2.text)
swap(&locationTuples[1].mapItem, &locationTuples[2].mapItem)
swap(&self.enterButtonArray.filter{$0.tag == 2}.first!.isSelected, &self.enterButtonArray.filter{$0.tag == 3}.first!.isSelected)
}
func showAlert(_ alertString: String) {
let alert = UIAlertController(title: nil, message: alertString, preferredStyle: .alert)
let okButton = UIAlertAction(title: "OK",
style: .cancel) { (alert) -> Void in
}
alert.addAction(okButton)
present(alert, animated: true, completion: nil)
}
// The remaining methods handle the keyboard resignation/
// move the view so that the first responders aren't hidden
func moveViewUp() {
if topMarginConstraint.constant != originalTopMargin {
return
}
topMarginConstraint.constant -= 165
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func moveViewDown() {
if topMarginConstraint.constant == originalTopMargin {
return
}
topMarginConstraint.constant = originalTopMargin
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
enterButtonArray.filter{$0.tag == textField.tag}.first!.isSelected = false
locationTuples[textField.tag-1].mapItem = nil
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
moveViewUp()
}
func textFieldDidEndEditing(_ textField: UITextField) {
moveViewDown()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
view.endEditing(true)
moveViewDown()
return true
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(locations.last!,
completionHandler: {(placemarks, error) -> Void in
if let placemarks = placemarks {
let placemark = placemarks[0]
self.locationTuples[0].mapItem = MKMapItem(placemark:
MKPlacemark(coordinate: placemark.location!.coordinate,
addressDictionary: placemark.addressDictionary as! [String:AnyObject]?))
self.sourceField.text = self.formatAddressFromPlacemark(placemark)
self.enterButtonArray.filter{$0.tag == 1}.first!.isSelected = true
}
})
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}