iOS master detail sec - ios

I'm working on a Contacts app which I'm developing using swift. I recently implemented sections and now the detail controller is not working properly. Whenever I click on a contact, it shows details for some other contact. I think the main problem is in prepareforsegue function but I can't figure it out. Help Pls!
//
// ContactListViewController.swift
// TechOriginators
//
// Created by Xcode User on 2017-10-09.
// Copyright © 2017 Xcode User. All rights reserved.
//
import UIKit
import Foundation
class ContactListViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating {
#IBOutlet var contactsTableView : UITableView!
var contactViewController: ContactViewController? = nil
var contacts : [Contact] = [] {
didSet{
self.contactsTableView.reloadData()
}
}
//Variables to implement sections in UITableView
var sectionLetters: [Character] = []
var contactsDict = [Character: [String]]()
var contactsName = [String]()
//Search Controller
let searchController = UISearchController(searchResultsController: nil)
//Variable to store filtered contacts through search
var filteredContacts = [Contact]()
//Function to show details of a contact record
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail"{
if let indexPath = contactsTableView.indexPathForSelectedRow{
let object = contacts[indexPath.row]
//let object = contactsDict[sectionLetters[indexPath.section]]![indexPath.row]
let controller = segue.destination as! ContactViewController
controller.detailItem = object
}
}
}
func createDict(){
contactsName = contacts.map{$0.FirstName}
//print(contactsName)
sectionLetters = contactsName.map{ (firstName) -> Character in
return firstName[firstName.startIndex]
}
sectionLetters.sorted()
sectionLetters = sectionLetters.reduce([], { (list, firstName) -> [Character] in
if !list.contains(firstName){
return list + [firstName]
}
return list
})
for entry in contactsName{
if contactsDict[entry[entry.startIndex]] == nil {
contactsDict[entry[entry.startIndex]] = [String]()
}
contactsDict[entry[entry.startIndex]]!.append(entry)
}
for (letter, list) in contactsDict{
contactsDict[letter] = list.sorted()
}
print(sectionLetters)
print(contactsDict)
}
// //Function to load contacts
func loadContacts()
{
self.contacts = getContacts()
}
/*private let session: URLSession = .shared
func loadContacts()
{
//let url = URL(string: "http://127.0.0.1:8080/api/Contacts")!
let url = URL(string: "http://10.16.48.237/api/Contacts")!
let task = session.dataTask(with: url) { (data, response, error) in
print("dataRecieved \(data)")
print("error \(error)")
print ("response \(response)")
guard let data = data else { return }
do {
self.contacts = try parse(data)
}
catch {
print("JSONParsing Error: \(error)")
}
}
task.resume() // firing the task
}*/
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
func numberOfSections(in tableView: UITableView) -> Int {
return sectionLetters.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if self.searchController.isActive {
return nil
} else {
return String(sectionLetters[section])
}
//return String(sectionLetters[section])
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
return filteredContacts.count
}
//return self.contacts.count
print(contactsDict[sectionLetters[section]]!.count)
return contactsDict[sectionLetters[section]]!.count
}
//Function to display cells in UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell" , for : indexPath)
let contact = contacts[indexPath.row]
let object: Contact
if searchController.isActive && searchController.searchBar.text != ""{
let contact = self.filteredContacts[indexPath.row]
cell.textLabel?.text = contact.FirstName + " " + contact.LastName
}else{
//contact = contactsDict[sectionLetters[indexPath.section]]![indexPath.row]
//cell.textLabel?.text = contact.FirstName + " " + contact.LastName
cell.textLabel?.text = contactsDict[sectionLetters[indexPath.section]]![indexPath.row]
}
return cell
}
//Function to update search results when the user types in search bar
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
func updateSearchResultsForSearchController(searchController: UISearchController){
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
//Function to find matches for text entered in search bar
func filterContentForSearchText(searchText: String){
filteredContacts = contacts.filter{p in
var containsString = false
if p.FirstName.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.LastName.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.Division.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.Department.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.BusinessNumber.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.HomePhone.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.CellularPhone.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.Role.lowercased().contains(searchText.lowercased()){
containsString = true
}
return containsString
}
contactsTableView.reloadData()
}
//Function to sorts contacts by First Name
func sortContacts() {
contacts.sort() { $0.FirstName < $1.FirstName }
contactsTableView.reloadData();
}
override func viewDidLoad() {
super.viewDidLoad()
loadContacts()
sortContacts()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
self.definesPresentationContext = true
createDict()
contactsTableView.tableHeaderView = searchController.searchBar
//contactsTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
// 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.
}
*/
//
// ContactViewController.swift
// TechOriginators
//
// Created by Xcode User on 2017-10-09.
// Copyright © 2017 Xcode User. All rights reserved.
//
import UIKit
class ContactViewController: UIViewController {
//#IBOutlet weak var detailDescriptionLabel: UILabel!
#IBOutlet weak var firstNameLabel: UILabel?
#IBOutlet weak var lastNameLabel: UILabel?
#IBOutlet weak var divisionLabel: UILabel?
#IBOutlet weak var departmentLabel: UILabel?
#IBOutlet weak var businessPhoneButton: UIButton?
#IBOutlet weak var homePhoneButton: UIButton?
#IBOutlet weak var cellularPhoneButton: UIButton?
#IBOutlet weak var roleLabel: UILabel?
/*#IBOutlet weak var firstNameLabel: UILabel?
#IBOutlet weak var lastNameLabel: UILabel?
#IBOutlet weak var phoneButton: UIButton?
#IBOutlet weak var emailButton: UIButton?*/
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
self.title = detail.FirstName + " " + detail.LastName
firstNameLabel?.text = detail.FirstName
lastNameLabel?.text = detail.LastName
divisionLabel?.text = detail.Division
departmentLabel?.text = detail.Department
businessPhoneButton?.setTitle(detail.BusinessNumber, for: .normal)
homePhoneButton?.setTitle(detail.HomePhone, for: .normal)
cellularPhoneButton?.setTitle(detail.CellularPhone, for: .normal)
roleLabel?.text = detail.Role
}
}
#IBAction func businessPhoneButtonPressed(sender: UIButton){
if let bPhone = detailItem?.BusinessNumber{
if let url = URL(string: "tel://\(bPhone)"), UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *){
UIApplication.shared.open(url)
}else{
UIApplication.shared.openURL(url)
}
}
}
}
#IBAction func homePhoneButtonPressed(sender: UIButton){
if let hPhone = detailItem?.HomePhone{
if let url = URL(string: "tel://\(hPhone)"), UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *){
UIApplication.shared.open(url)
}else{
UIApplication.shared.openURL(url)
}
}
}
}
#IBAction func cellularPhoneButtonPressed(sender: UIButton){
if let cPhone = detailItem?.CellularPhone{
/*if let url = NSURL(string: "tel://\(phone)"){
UIApplication.shared.open(url as URL, options: [:], completionHandler: nil)
}*/
if let url = URL(string: "tel://\(cPhone)"), UIApplication.shared.canOpenURL(url) {
if #available(iOS 10, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
}
}
}
/*#IBAction func emailButtonPressed(sender: UIButton){
if let email = detailItem?.email{
if let url = URL(string:"mailto:\(email)"){
UIApplication.shared.open(url as URL)
}
}
}*/
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
configureView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var detailItem: Contact? {
didSet {
// Update the view.
self.configureView()
}
}
/*
// 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.
}
*/
}

I suggest using the same data structure for all your table operations to be consistent. Try populating your dictionary as:
var contactsDict = [Character: [Contact]]()
That way, you can always use the section to find the right array and the row to find the right offset in the array.
Separating the names from the contacts in the table's data is inviting them to get out of synch.

Related

getting the error of NSUnknownKeyException', reason: setValue:forUndefinedKey: this class is not key value coding-compliant for the key description

I am facing the issue of passing the data from HomeViewController to PostDetailViewController,
I have checked the classes connected to the View Controllers are correct, class connected to the XIB file is PostTableViewCell,
and still getting this error of
'NSUnknownKeyException', reason:
'[
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key description
upon clicking the tablecell
HOMEVIEWCONTROLLER
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView:UITableView!
var posts = [Post]()
var db: Firestore!
var postKey:String = ""
private var documents: [DocumentSnapshot] = []
//public var posts: [Post] = []
private var listener : ListenerRegistration!
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
self.navigationController?.navigationBar.isTranslucent = false
tableView = UITableView(frame: view.bounds, style: .plain)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
tableView.backgroundColor = UIColor(white: 0.90,alpha:1.0)
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
// Fallback on earlier versions
layoutGuide = view.layoutMarginsGuide
}
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
retrieveAllPosts()
//checkForUpdates()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func handleLogout(_ sender:Any) {
try! Auth.auth().signOut()
self.dismiss(animated: false, completion: nil)
}
func retrieveAllPosts(){
let postsRef = Firestore.firestore().collection("posts").limit(to: 50)
postsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
// self.postKey = document.documentID
let username = data["username"] as? String ?? ""
let postTitle = data["postTitle"] as? String ?? ""
let postcategory = data["postcategory"] as? String ?? ""
let postContent = data["postContent"] as? String ?? ""
let newSourse = Post( _documentId: document.documentID, _username: username, _postTitle: postTitle, _postcategory: postcategory, _postContent: postContent)
self.posts.append(newSourse)
print(self.postKey)
}
self.tableView.reloadData()
}
}
}
}
/* postsRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.posts = querySnapshot!.documents.flatMap({Post(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}*/
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let post = self.posts[indexPath.row]
//print("row selected: \(indexPath.row)")
//Swift.print(post._documentId!)
let postKey = post._documentId
let postName = post._username
print(postKey! + postName!)
performSegue(withIdentifier: "toDetailView", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//segue.forward(posts, to: segue.destination)
guard let details = segue.destination as? PostDetailViewController,
let index = tableView.indexPathForSelectedRow?.row
else {
return
}
details.detailView = posts[index]
}
}
POSTTABLEVIEWCELL
class PostTableViewCell: UITableViewCell {
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var subtitleLabel: UILabel!
#IBOutlet weak var postTextLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
// profileImageView.layer.cornerRadius = profileImageView.bounds.height / 2
// profileImageView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func set(post:Post) {
usernameLabel.text = post._username
postTextLabel.text = post._postTitle
subtitleLabel.text = post._postcategory
}
}
POST DETAIL VIEW CONTROLLER
class PostDetailViewController: UIViewController {
#IBOutlet var usernamelabel: UILabel!
#IBOutlet var posttitlelabel: UILabel!
#IBOutlet var postIdlabel: UILabel!
// #IBOutlet var description: UILabel!
#IBOutlet var postcategorylabel: UILabel!
var detailView: Post?
var postId:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
postIdlabel?.text = detailView?._documentId
posttitlelabel?.text = detailView?._postTitle
}
/*
// 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.destination.
// Pass the selected object to the new view controller.
}
*/
}
This situations usually happens when you have already set IBOutlet in your XIB file and you comment out it's connecting outlets in code.
Here in your case, In your PostDetailViewController
// #IBOutlet var description: UILabel!
You have commented the description Label, but IBOutlet is still connected in your XIB file.
So, Look for your XIB file and checkout for active IBOutlet Connections and remove it for description label, Clean, Build and Run.
Hope it helps.

How do I get new items to display in List View

I am building an app in Xcode 9.4 and I am having trouble getting new items to display in a list view after creating it in another view controller. The item shows up in CoreData, but in order for the item to show in the list view I have to back out of the list view and then return to see it in the list.
I have added the Protocol and Delegate methods but something is still missing from the formula.
Here is the code from the DetailViewController where the new item is added:
import UIKit
import CoreData
protocol DetailViewControllerDelegate: class {
func detailViewController(_ controller: DetailViewController, didFinishAdding task: Task)
// func detailViewController(_ controller: DetailViewController, didFinishEditing task: Task)
}
class DetailViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
var tasks = [Task]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var selectedTask: String?
var taskDetails: String?
var taskCategory: Category?
#IBOutlet weak var taskTitle: UITextField!
#IBOutlet weak var taskDetail: UITextView!
var taskToEdit: Task?
weak var delegate: DetailViewControllerDelegate? // Required to carry out the protocol methods
override func viewDidLoad() {
super.viewDidLoad()
taskTitle.text = selectedTask
taskDetail.text = taskDetails
taskDetail.textColor = UIColor.lightGray
navigationItem.largeTitleDisplayMode = .never
}
func textViewDidBeginEditing(_ textView: UITextView) {
if taskDetail.textColor == UIColor.lightGray {
taskDetail.text = nil
taskDetail.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if taskDetail.text.isEmpty {
taskDetail.text = "Details..."
taskDetail.textColor = UIColor.lightGray
}
}
override func viewWillAppear(_ animated: Bool) {
taskTitle.becomeFirstResponder()
}
#IBAction func doneButtonPressed(_ sender: UIBarButtonItem) {
let newTask = Task(context: self.context)
newTask.title = taskTitle.text!
newTask.details = taskDetail.text!
newTask.parentCategory = taskCategory!
delegate?.detailViewController(self, didFinishAdding: newTask)
// self.taskArray.append(newTask)
self.saveTasks()
navigationController?.popViewController(animated: true)
}
func saveTasks() {
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
}
}
Here is the code from the List View controller, which should display the new item as soon as it is created:
import UIKit
import CoreData
class TaskListTableViewController: UITableViewController, DetailViewControllerDelegate {
var tasks = [Task]()
var selectedCategory: Category? {
didSet {
loadTasks()
}
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.largeTitleDisplayMode = .never
title = selectedCategory?.name
loadTasks()
tableView.reloadData()
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
func detailViewController(_ controller: DetailViewController, didFinishAdding task: Task) {
let newRowIndex = tasks.count
tasks.append(task)
let indexPath = IndexPath(row: newRowIndex, section: 0)
let indexPaths = [indexPath]
tableView.insertRows(at: indexPaths, with: .automatic)
tableView.reloadData()
navigationController?.popViewController(animated: true)
}
// TODO: finish this
func detailViewController(_ controller: DetailViewController, didFinishEditing task: Task) {
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath)
let task = tasks[indexPath.row]
let label = cell.viewWithTag(1001) as! UILabel
label.text = task.title
return cell
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AddItem" {
let destinationVC = segue.destination as! DetailViewController
destinationVC.taskCategory = selectedCategory
} else if segue.identifier == "EditItem" {
let destinationVC = segue.destination as! DetailViewController
destinationVC.taskCategory = selectedCategory
let indexPath = tableView.indexPathForSelectedRow!
let task = tasks[indexPath.row]
destinationVC.selectedTask = task.title
destinationVC.taskDetails = task.details
}
}
func loadTasks(with request: NSFetchRequest<Task> = Task.fetchRequest(), predicate: NSPredicate? = nil) {
let categoryPredicate = NSPredicate(format: "parentCategory.name MATCHES %#", selectedCategory!.name!)
request.predicate = categoryPredicate
do {
tasks = try context.fetch(request)
} catch {
print("Error fetching data \(error)")
}
tableView.reloadData()
}
#IBAction func addTaskButton(_ sender: UIBarButtonItem) {
print("new task added")
}
}
You need to set the delegate in prepareForSegue
destinationVC.delegate = self

How to segue back to filled-in version of TableViewController (Swift)?

I am building a room-booking app for iOS in Xcode 9.3.
Here is the basic layout:
First TableViewController(TVC1): starts empty. Pressing '+' pops up the
Second TableViewController(TVC2) with many fields to fill in.
Once the 'Done' button on TVC2 is pressed I get back to TVC1 which now has a cell (Subtitle style) containing the details inserted.
I would now like to tap on said cell and get back to TVC2 to either check or modify the data.
I have created the segue but upon tapping I get the same version of TVC2 that I get when pressing '+', not the filled in one.
What am I doing wrong?
This is the code relative to TVC1 that I need to edit:
import UIKit
class RegistrationTableViewController: UITableViewController {
var registrations: [Registration] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return registrations.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RegistrationCell", for: indexPath)
let registration = registrations[indexPath.row]
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
cell.textLabel?.text = registration.firstName + " " + registration.lastName
cell.detailTextLabel?.text = dateFormatter.string(from: registration.checkInDate) + " - " + registration.roomType.name
return cell
}
#IBAction func unwindFromAddRegistration(unwindSegue: UIStoryboardSegue) {
guard let addRegistrationTableViewController = unwindSegue.source as? AddRegistrationTableViewController,
let registration = addRegistrationTableViewController.registration else { return }
registrations.append(registration)
tableView.reloadData()
}
// MARK: Challenge.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ViewReservationDetails" {
}
}
And here is the code for (TVC2) so that you have more or less all the app available.
import UIKit
class AddRegistrationTableViewController: UITableViewController, SelectRoomTypeTableViewControllerDelegate {
// MARK: Properties
let checkInDatePickerCellIndexPath = IndexPath(row: 1, section: 1)
let checkOutDatePickerCellIndexPath = IndexPath(row: 3, section: 1)
var isCheckInDatePickerShown: Bool = false {
didSet {
checkInDatePicker.isHidden = !isCheckInDatePickerShown
}
}
var isCheckOutDatePickerShown: Bool = false {
didSet {
checkOutDatePicker.isHidden = !isCheckOutDatePickerShown
}
}
var roomType: RoomType?
var registration: Registration? {
guard let roomType = roomType else { return nil }
let firstName = firstNameTextField.text ?? ""
let lastName = lastNameTextField.text ?? ""
let email = emailTextField.text ?? ""
let checkInDate = checkInDatePicker.date
let checkOutDate = checkOutDatePicker.date
let numberOfAdults = Int(numberOfAdultsStepper.value)
let numberOfChildren = Int(numberOfChildrenStepper.value)
let hasWifi = wifiSwitch.isOn
return Registration(firstName: firstName, lastName: lastName, emailAddress: email, checkInDate: checkInDate, checkOutDate: checkOutDate, numberOfAdults: numberOfAdults, numberOfChildren: numberOfChildren, roomType: roomType, wifi: hasWifi)
}
var selectedItem: Registration?
// MARK: Outlets
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var checkInDateLabel: UILabel!
#IBOutlet weak var checkInDatePicker: UIDatePicker!
#IBOutlet weak var checkOutDateLabel: UILabel!
#IBOutlet weak var checkOutDatePicker: UIDatePicker!
#IBOutlet weak var numberOfAdultsLabel: UILabel!
#IBOutlet weak var numberOfAdultsStepper: UIStepper!
#IBOutlet weak var numberOfChildrenLabel: UILabel!
#IBOutlet weak var numberOfChildrenStepper: UIStepper!
#IBOutlet weak var roomTypeLabel: UILabel!
#IBOutlet weak var wifiSwitch: UISwitch!
// MARK: Actions
#IBAction func datePickerValueChanged(_ sender: UIDatePicker) {
updateDateViews()
}
#IBAction func stepperValueChanged(_ sender: UIStepper) {
updateNumberOfGuests()
}
#IBAction func wifiSwitchChanged(_ sender: UISwitch) {
// implemented later
}
#IBAction func cancelButtonTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
// MARK: Methods
func updateDateViews() {
checkOutDatePicker.minimumDate = checkInDatePicker.date.addingTimeInterval(86400)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
checkInDateLabel.text = dateFormatter.string(from: checkInDatePicker.date)
checkOutDateLabel.text = dateFormatter.string(from: checkOutDatePicker.date)
}
func updateNumberOfGuests() {
numberOfAdultsLabel.text = "\(Int(numberOfAdultsStepper.value))"
numberOfChildrenLabel.text = "\(Int(numberOfChildrenStepper.value))"
}
func updateRoomType() {
if let roomType = roomType {
roomTypeLabel.text = roomType.name
} else {
roomTypeLabel.text = "Not Set"
}
}
func didSelect(roomType: RoomType) {
self.roomType = roomType
updateRoomType()
}
override func viewDidLoad() {
super.viewDidLoad()
let midnightToday = Calendar.current.startOfDay(for: Date())
checkInDatePicker.minimumDate = midnightToday
checkInDatePicker.date = midnightToday
updateDateViews()
updateNumberOfGuests()
updateRoomType()
}
// MARK: TableView Data
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch (indexPath.section, indexPath.row) {
case (checkInDatePickerCellIndexPath.section, checkInDatePickerCellIndexPath.row):
if isCheckInDatePickerShown {
return 216.0
} else {
return 0.0
}
case (checkOutDatePickerCellIndexPath.section, checkOutDatePickerCellIndexPath.row):
if isCheckOutDatePickerShown {
return 216.0
} else {
return 0.0
}
default:
return 44.0
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch (indexPath.section, indexPath.row) {
case (checkInDatePickerCellIndexPath.section, checkInDatePickerCellIndexPath.row - 1):
if isCheckInDatePickerShown {
isCheckInDatePickerShown = false
} else if isCheckOutDatePickerShown {
isCheckOutDatePickerShown = false
isCheckInDatePickerShown = true
} else {
isCheckInDatePickerShown = true
}
tableView.beginUpdates()
tableView.endUpdates()
case (checkOutDatePickerCellIndexPath.section, checkOutDatePickerCellIndexPath.row - 1):
if isCheckOutDatePickerShown {
isCheckOutDatePickerShown = false
} else if isCheckInDatePickerShown {
isCheckInDatePickerShown = false
isCheckOutDatePickerShown = true
} else {
isCheckOutDatePickerShown = true
}
tableView.beginUpdates()
tableView.endUpdates()
default:
break
}
}
// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SelectRoomType" {
let destinationViewController = segue.destination as? SelectRoomTypeTableViewController
destinationViewController?.delegate = self
destinationViewController?.roomType = roomType
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The challenge to this tutorial says to start from here and:
"Update the RegistrationTableViewController(TVC1) with a segue that allows the user to select and view the details of a registration in the AddRegistrationTableViewController(TVC2).
Hope this helps to provide the right solution.
Create a property let say selectedItem in TVC2 screen.
var selectedItem: Registration?
Modify prepare for cell ...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ViewReservationDetails" {
let sourceCell = sender as! UITableViewCell
if let indexPath = tableView.indexPath(for: sourceCell) {
if let dest = segue.destination as? <#Destination view controller#> {
dest.selectedItem = registrations[indexPath.row]
}
}
}
}
And finally in your destination view controller (having TVC2) you need to check selectedItem and assign value in viewDidLoad: or wherever.
override func viewDidLoad() {
super.viewDidLoad()
if let item = selectedItem {
//here set value as you want to views
}
}
Use this code in tableView(_:didSelectRowAt:):
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "ViewReservationDetails", sender: indexPath)
}
Then, in TVC1’s prepare(for:sender:) you pass the selected registrations element plus a completion handler to TVC2, like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ViewReservationDetails" {
if let destination = segue.destination as? AddRegistrationTableViewController, let indexPath = sender as? IndexPath {
destination.registration = registrations[indexPath.row]
destination.completionHandler = { (registration) in
self.registrations.append(registration)
self.tableView.reloadData()
}
}
}
}
In TVC2, you declare a property completionHandler like this:
var completionHandler: ((Registration) -> Void)?
and you call this handler in viewWillDisappear:
completionHandler?(newRegistration)
with newRegistration being the newly created element to be added to the Registration array. This way, you won’t need an unwind segue.

SearchController issue, when search the displayController shows a spacing from the Searchbar

The issue is this:
In the storyboard, I must uncheck the Adjust Scroll View Insets, because if not do this, I will get a other issue(https://stackoverflow.com/questions/40974647/uisearchcontroller-issue-nslayoutattribute-do-not-work-in-real-device), and I don't know this if is affect the issue here.(I test in simulator, if check Adjust Scroll View Insets, the issue here will not appear )
My code
import UIKit
import SVProgressHUD
class ChooseStoreViewController: UIViewController,UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
#IBOutlet weak var tableView: UITableView!
var ori_dataSource: [StoreListModel] = [StoreListModel]()
var dataSource = [String]()
var filterdDataSource = [String]()
var resultSearchController = UISearchController()
var choosedStore:StoreListModel? = nil
var userInfoFromChooseTerant:[String:Any]?
#IBOutlet weak var top_constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
initData()
initUI()
}
// MARK: - view life
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.isNavigationBarHidden = true
}
func initData() {
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.resultSearchController.searchBar.placeholder = "search"
self.resultSearchController.searchBar.tintColor = UIColor.black
self.resultSearchController.searchBar.delegate = self
self.tableView.tableHeaderView = self.resultSearchController.searchBar
let nib = UINib(nibName: "TerantListCell", bundle: nil)
// Required if our subclasses are to use: dequeueReusableCellWithIdentifier:forIndexPath:
//tableView.register(nib, forCellReuseIdentifier: "TerantListCell")
self.tableView.register(nib, forCellReuseIdentifier: "TerantListCell")
self.tableView.tableFooterView = UIView.init()
self.tableView.reloadData()
networkForStoreList()
}
func initUI() {
let backNavItem:UIBarButtonItem = UtilSwift.addBackButtonItem(nil, controlelr: self)
backNavItem.action = #selector(navBack)
// print(userInfoFromChooseTerant!)
tableView.separatorStyle = UITableViewCellSeparatorStyle.none
}
// 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.
let chooseRole: ChooseRoleViewController = segue.destination as! ChooseRoleViewController
chooseRole.userInfoFromChooseStore = self.userInfoFromChooseTerant
}
// MARK: - search delegate
func searchBarCancelButtonClicked() {
for item:NSLayoutConstraint in self.tableView.constraints {
self.view.setNeedsLayout()
if item.firstAttribute == NSLayoutAttribute.top {
item.constant = 0
}
}
}
// MARK: - searchbar delegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.setValue("cancel", forKey:"_cancelButtonText")
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
}
// MARK: - private methods
func navBack() {
_ = self.navigationController?.popViewController(animated: true)
}
// MARK: - actions
#IBAction func unwindToChooseStoreVCFromChooseRole(segue: UIStoryboardSegue){
}
#IBAction func nextStepAction(_ sender: UIButton) {
/*if choosedStore == nil {
let lml_alert: LMLDropdownAlertView = LMLDropdownAlertView.init(frame: self.view.bounds)
lml_alert.showAlert(title: Global.hint, detail_Title: "select", cancleButtonTitle: "cacnel", confirmButtonTitle: "confirm", action: { (button) in
})
return
}*/
self.resultSearchController.isActive = false
if self.choosedStore != nil {
_ = self.userInfoFromChooseTerant?.updateValue(self.choosedStore!.userId, forKey: "store_id")
}
self.performSegue(withIdentifier: "ChooseStoreVCToChooseRoleVC", sender: self)
}
// MARK: - network
func networkForStoreList() {
let params:[String:String] = [
"createTime":"-1",
"userId" : self.userInfoFromChooseTerant!["affiliated_id"] as! String
]
// url_terantList
Mysevers.afpost(withHud: true, andAddressname: Global.url_listStore, parmas: params, requestSuccess: { (result) in
let stateCode = UtilSwift.getNetStateCode(result: result as Any, key: Global.net_key_stateCode)
if stateCode == 0 {
let storeArr:[[String : Any]] = UtilSwift.getNetAnyObject(result: result as Any, key: "list") as! [[String : Any]] // Global.net_key_bussines
//self.ori_dataSource = terantArr
for item:[String: Any] in storeArr {
let store_list_model: StoreListModel = StoreListModel.initStoreListModelWithDic(dic: item)
self.ori_dataSource.append(store_list_model)
}
for item:StoreListModel in self.ori_dataSource {
self.dataSource.append(item.name)
}
self.tableView.reloadData()
}else if stateCode == -1 {
SVProgressHUD.showError(withStatus: "err")
}
}, failBlcok: {
SVProgressHUD.showError(withStatus: "err")
})
}
// MARK: - tableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.resultSearchController.isActive {
return filterdDataSource.count
}else {
return dataSource.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TerantListCell = tableView.dequeueReusableCell(withIdentifier: "TerantListCell", for: indexPath) as! TerantListCell
if self.resultSearchController.isActive {
cell.title_label.text = self.filterdDataSource[indexPath.row]
}else {
cell.title_label?.text = self.dataSource[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.choosedStore = self.ori_dataSource[indexPath.row]
}
// MARK: - regexp
func updateSearchResults(for searchController: UISearchController) {
self.filterdDataSource.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.dataSource as NSArray).filtered(using: searchPredicate)
self.filterdDataSource = array as! [String]
self.tableView.reloadData()
}
}
Go to ".storyboard" file where "ChooseStoreViewController" exist. Then click on UITableView and change tableView constraints as follows:
Check Top Space constraint.

Data being passed from UITableView to new Controller, SWIFT

I've been at this for a while and I can not figure out how to pass data from the table view cell to a new "detail" controller. I understand the segue that connects it to a new controller but setting variables in that class to some values fails (doesn't show the values). This is what I currently have:
import UIKit
class BuyTreeViewController: UITableViewController, UISearchBarDelegate, UISearchDisplayDelegate {
var products : [Product] = []
var filteredProducts = [Product]()
override func viewDidLoad() {
self.products = [
Product(name:"Chocolate", price: 11, description: "i"),
Product(name:"Candy", price: 12, description: "h"),
Product(name:"Break", price: 13, description: "g"),
Product(name:"Apple", price: 14, description: "f"),
Product(name:"Computer", price: 15, description: "e"),
Product(name:"Laptop", price: 16, description: "d"),
Product(name:"Cup", price: 17, description: "c"),
Product(name:"Table", price: 18, description: "b"),
Product(name:"Chair", price: 19, description: "a")
]
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.searchDisplayController!.searchResultsTableView {
return self.filteredProducts.count
} else {
return self.products.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
var product : Product
// Check to see whether the normal table or search results table is being displayed and set the Candy object from the appropriate array
if tableView == self.searchDisplayController!.searchResultsTableView {
product = filteredProducts[indexPath.row]
} else {
product = products[indexPath.row]
}
// Configure the cell
cell.textLabel!.text = product.name
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
func filterContentForSearchText(searchText: String, scope: String = "All") {
self.filteredProducts = self.products.filter(
{
( product : Product) -> Bool in
var categoryMatch = (scope == "All") || (product.category == scope)
var stringMatch = product.name.rangeOfString(searchText)
return categoryMatch && (stringMatch != nil)
})
}
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
let scopes = self.searchDisplayController!.searchBar.scopeButtonTitles as [String]
let selectedScope = scopes[self.searchDisplayController!.searchBar.selectedScopeButtonIndex] as String
self.filterContentForSearchText(searchString, scope: selectedScope)
return true
}
func searchDisplayController(controller: UISearchDisplayController!,
shouldReloadTableForSearchScope searchOption: Int) -> Bool {
let scope = self.searchDisplayController!.searchBar.scopeButtonTitles as [String]
self.filterContentForSearchText(self.searchDisplayController!.searchBar.text, scope: scope[searchOption])
return true
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("productDetail", sender: tableView)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "productDetail" {
// let productDetailViewController = segue.destinationViewController as UIViewController
let productDetailViewController = segue.destinationViewController as ProductDetailViewController
productDetailViewController.testLabel.text = "123345"
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()!
let destinationTitle = self.filteredProducts[indexPath.row].name
productDetailViewController.title = destinationTitle
} else {
let indexPath = self.tableView.indexPathForSelectedRow()!
let destinationTitle = self.products[indexPath.row].name
productDetailViewController.title = destinationTitle
}
productDetailViewController.productPriceText = "10"
productDetailViewController.productTitleText = "100"
productDetailViewController.productDescriptionText = "100"
}
}
And this is what I have in the ProductDetailViewController:
import UIKit
class ProductDetailViewController: UIViewController {
#IBOutlet weak var productDescription: UITextView!
#IBOutlet weak var productPrice: UITextField!
#IBOutlet weak var productTitle: UITextField!
#IBOutlet weak var connectUserButton: UIButton!
#IBOutlet weak var testLabel: UILabel!
// var productDescriptionText: String
// var productPriceText: String
// var productTitleText: String
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// productDescription.text = productDescriptionText
// productPrice.text = productPriceText
// productTitle.text = productTitleText
}
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 prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
#IBAction func contactUserClicked(sender: AnyObject) {
println("conentUserButton clicked")
}
For your prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "productDetail" {
let indexPath = self.tableView.indexPathForSelectedRow()
let theSelectedRow = filteredProducts[indexPath!.row]
let theDestination = segue.destinationViewController as ProductDetailViewController
theDestination.productPriceText = "10"
theDestination.productTitleText = "100"
theDestination.productDescriptionText = "100"
} else
//only needed if more than one segue
}
}
Your detailViewController looked good to me. If you still have issues, I will post a more complete example.

Resources