I turn on the editing table in the FirstViewController
#IBAction func editButtonPressed(sender: UIBarButtonItem) {
self.tableView.allowsMultipleSelectionDuringEditing = true
if self.editing {
let popoverEditMenu = self.storyboard?.instantiateViewControllerWithIdentifier("popoverEditMenu") as! EditMenuTableViewController
popoverEditMenu.modalPresentationStyle = .Popover
popoverEditMenu.popoverPresentationController!.delegate = self
let popover: UIPopoverPresentationController = popoverEditMenu.popoverPresentationController!
popover.barButtonItem = sender
presentViewController(popoverEditMenu, animated: true, completion: nil)
} else {
editButton.image = UIImage(named: "profile_more")
self.editing = !self.editing
}
}
Editing table is included successfully. After the above actions, I want to finish editing, by clicking on a table cell in a popover, code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let firstTableVC = self.storyboard?.instantiateViewControllerWithIdentifier("firstTableVC") as! FirstTableViewController
tableView.deselectRowAtIndexPath(indexPath, animated: true)
switch indexPath.row {
case 0:
self.dismissViewControllerAnimated(true, completion: nil)
firstTableVC.editing = false // Disable Editing
firstTableVC.editButton.image = UIImage(named: "1461294921_15.Pencil")
default:
break
}
}
But there is no change in the button image, and table editing mode not disabled
Solution found!
The problem was solved by the use of delegation. Thanks to #pbasdf for the tip
import UIKit
protocol SecondTableViewControllerDelegate {
func endEditing()
}
class SecondTableViewController: UITableViewController {
var delegate: SecondTableViewControllerDelegate?
...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
switch indexPath.row {
case 0:
self.dismissViewControllerAnimated(true, completion: nil)
delegate?.endEditing()
default:
break
}
}
}
Delegate function in FirstViewController. You need to specify a delegate inheritance in FirstViewController
func endEditing() {
self.editing = false
editButton.image = UIImage(named: "1461294921_15.Pencil")
}
Try to use
firstTableVC.editButton.setImage(UIImage(named:"1461294921_15.Pencil"), forState: UIControlState.Normal)
It must works.
Related
I am trying to get data from a different screen but for some reason, the variable does not update. here is the code so far
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewController: UIViewController = UIViewController()
switch (indexPath.row) {
case 0:
print("hello")
let vc = AddViewController()
vc.chosenDaily = true
dismiss(animated: true, completion: nil)
here I created a tableView and once the user clicks on the first index of my tableView I want to set a boolean variable to true and return to another screen. I declared this boolean variable on the other screen here.
var chosenDaily:Bool = false
on the same screen, I have a save button and want to print the result.
#IBAction func didTapSaveButton() {
if chosenDaily == true {
print("it's true")
} else if chosenDaily == false {
print("it's false")
}
}
but for some reason, the code doesn't update and returns false instead of true. can anyone help? thanks for any help that i get
I don't know if you are using navigation controller during the transition between screens. But I suppose you are using it. So let's say you have two ViewController. FirstViewController and SecondViewController. And they are also using navigation controller when you make a transition from FirstViewController to the SecondViewController. In the SecondViewController you have your tableView and when you click a row in that tableView you want to send some value to the FirstViewController before dismiss. Add this function in the SecondViewController and call it before dismiss :
func changeValue(myValue : Bool) {
if let navController = SecondViewController as? UINavigationController {
let getfirstVC = navController.topViewController as! FirstViewController
getfirstVC.chosenDaily = myValue
}
}
and your tableview function will be like this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewController: UIViewController = UIViewController()
switch (indexPath.row) {
case 0:
print("hello")
changeValue(true)
dismiss(animated: true, completion: nil)
Don't forget to change FirstViewController and SecondViewController according to your own.
You can use Delegate to interactive between view.
protocol tableViewControllerDelegate {
func change(value:Bool)
}
class tableViewController: UIViewController {
var del:tableViewControllerDelegate!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (indexPath.row) {
case 0:
del?.change(value:true)
dismiss(animated: true, completion: nil)
break;
default:
break
}
}
}
Class is reference type, You can also processing data by class.
class AddViewController:UIViewController,tableViewControllerDelegate{
var chosenDaily = false
func change(value:Bool){
chosenDaily = value
}
}
class tableViewController: UIViewController {
var data:Sample!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (indexPath.row) {
case 0:
data?.chosenDaily = true
dismiss(animated: true, completion: nil)
break;
default:
break
}
}
}
class Sample{
var chosenDaily = false
}
class AddViewController:UIViewController{
var data = Sample()
func open(){
let view = tableViewController()
view.data = data
}
}
I want to avoid duplicate rewriting same code and create reusable UITableViewController.
I have ExerciseViewController with 3 buttons. Each button push a UITableViewController on the navigation stack. There are three UITableViewControllers: 1) CategoryUITableVC, 2) EquipmentUITableVC, 3) MusclesUITableVC.
All of these three view controllers have almost exactly same layout - cells with labels and accessory buttons. The only difference is that first view controller has got image next to title. Is it worth doing one reusable VC and instantiate it 3 times or maybe better solution create 3 separated VC (but It will be just rewriting almost same code).
I use coordinator pattern.
class ExerciseCoordinator: NSObject, Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
...
//unnecessary code to show
...
// *HERE I INSTANTIATE VIEW CONTROLLERS, I PRESENT THEM MODALLY BUT I WANT TO HAVE NAVIGATION BAR, SO I NEED TO CREATE NEW NAVIGATION CONTROLLERS*
lazy var equipmentVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var equipmentNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: equipmentVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
lazy var categoryVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var categoryNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: categoryVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
lazy var muscleVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var muscleNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: muscleVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
}
extension ExerciseCoordinator: CustomExerciseDelegate {
func selectCategory() {
navigationController.viewControllers.last?.present(categoryNavController, animated: true, completion: nil)
categoryVC.dataType = .category
}
func selectEquipment() {
navigationController.viewControllers.last?.present(equipmentNavController, animated: true, completion: nil)
equipmentVC.dataType = .equipment
}
func selectMuscles() {
navigationController.viewControllers.last?.present(muscleNavController, animated: true, completion: nil)
muscleVC.dataType = .muscle
}
}
I assign data type to know from where it comes from (CategoryVC/EquipmentVC/MuscleVC) when I will dismiss UITableVC.
Here it is my reusable UITableViewController:
import UIKit
import RealmSwift
class ExerciseCategoryTableViewController: UITableViewController {
var delegate: ExerciseSelectionCriteriaDelegate?
//I use it delegate to send data back after dismiss view
var dataType: DataType?
var data = [String]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData() }}
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
func getData() {
if dataType == .category {
let allCategories = RealmService.shared.realm.objects(Category.self)
data = allCategories.compactMap({$0.category})
} else if dataType == .equipment {
let allEquipment = RealmService.shared.realm.objects(Equipment.self)
data = allEquipment.compactMap({$0.equipment})
} else {
let allMuscles = RealmService.shared.realm.objects(Muscles.self)
data = allMuscles.compactMap({$0.muscles})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// below is my own shortcut for dequeue cell, it works
let cell: ExerciseSelectionTableViewCell = tableView.dequeueResuableCell(for: indexPath)
cell.category.text = data[indexPath.row]
if let image = UIImage(named: "\(data[indexPath.row].lowercased())") {
cell.categoryImage.image = image
cell.categoryImage.isHidden = false
} else {
cell.categoryImage.isHidden = true
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
#IBAction func closeViewController(_ sender: UIBarButtonItem) {
closeViewController()
}
#IBAction func saveSelectedCategories(_ sender: UIBarButtonItem) {
saveSelectedData()
}
func saveSelectedData() {
let selectedIndexes = tableView.indexPathsForSelectedRows
if let selectedData = selectedIndexes?.compactMap({data[$0.row]}) {
dismiss(animated: true) {
self.delegate?.selectedFields(for: selectedData, dataType: self.dataType)
}
} else {
dismiss(animated: true) {
self.delegate?.selectedFields(for: nil, dataType: self.dataType)
}
}
}
func closeViewController() {
guard let _ = tableView.indexPathsForSelectedRows else { return dismiss(animated: true, completion: nil)}
Alert.showAlertWithCompletion(on: self, with: "TEXT", message: "TEXT") { _ in
self.dismiss(animated: true, completion: nil)
}
}
}
I will be thankful if you tell me if this approach is correct or maybe better solution are separated view controllers or create protocol with default implementation?
Expected:
When a UIButton is tapped, show a viewcontroller modally that has a search controller and tableview with results.
When tapping on an item in the list, change the text of the search bar to what was tapped and dismiss the viewcontroller back to the original with the UIButton now set to that text.
Actual:
UIButton calls a segue to the searchViewController.
searchViewController shows, and configures the searchController and tableView correctly.
Tapping on a cell calls the exit segue that unwinds to the original screen and correctly updates the text in the UIButton and searchbar...
but, a freaking white screen lags on the unwind segue and its driving me crazy.
Mitigation tried:
Resigning the searchController then calling the segue
programmatically
Calling self.dismiss(animated: true completion:nil) in didSelectRowAt
Putting the dismiss on the main thread with: DispatchQueue.main.async { }
calling self.presentingViewController?.dismiss(animated: true)
Video Demo of flashing
Code:
SearchDetailsViewController - the Viewcontroller to unwind to
import UIKit
class SearchDetailsViewController: UIViewController {
#IBOutlet weak var destinationButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if searchDestination != "" {
destinationButton.setTitle(searchDestination, for: UIControlState.normal)
destinationButton.setTitleColor(UIColor.black, for: UIControlState.normal)
} else {
destinationButton.setTitle("Search Nearby", for: UIControlState.normal)
}
}
#IBAction func unwindToSearchDetailsViewController(segue: UIStoryboardSegue){
}
}
SearchViewController - the problem child. I currently have the tableview cell as the exit segue in storyboard.
class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, UISearchControllerDelegate {
#IBOutlet weak var searchResultsTableView: UITableView!
var destinationsObj:[String:[String]] = [:]
var destinations:[String] = []
var defaultDestinations:[String] = ["Search Nearby"]
var filteredDestinations:[String] = ["Search Nearby"]
var shouldShowSearchResults = false
var searchActive:Bool = false
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
defaultDestinations = recentSearches
configureTableView()
configureSearchController()
}
override func viewDidAppear(_ animated: Bool) {
// Show search bar keyboard
searchController.isActive = true
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: Configure Functions
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil) //nil lets the view controller also be the search results
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Where to?"
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()
searchResultsTableView.tableHeaderView = searchController.searchBar
searchController.delegate = self
}
func configureTableView() {
searchResultsTableView.delegate = self
searchResultsTableView.dataSource = self
//searchResultsTableView.isMultipleTouchEnabled = false
}
// MARK: TableView Delegate Functions
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowSearchResults {
return filteredDestinations.count
} else {
return defaultDestinations.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "idCell", for: indexPath)
if shouldShowSearchResults {
cell.textLabel?.text = filteredDestinations[indexPath.row]
} else {
cell.textLabel?.text = defaultDestinations[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let value = tableView.cellForRow(at: indexPath)?.textLabel?.text {
self.searchController.searchBar.text = value
searchDestination = value
if !recentSearches.contains(value) {
recentSearches.append(value)
}
}
//self.searchController.resignFirstResponder()
// tableView.deselectRow(at: indexPath, animated: false)
// DispatchQueue.main.async {
// self.dismiss(animated: true, completion: nil)
// }
// self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
// MARK: UISearchBar Delegate Functions
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let value = searchBar.text {
searchDestination = value
if !recentSearches.contains(value) {
recentSearches.append(value)
}
}
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
shouldShowSearchResults = true
if searchText.characters.count > 1 {
return
} else {
if let firstLetter = searchText.characters.first{
print("Typed \(firstLetter)")
getPredictionData(firstLetter:firstLetter.description)
}
}
}
func dismissCurrentView() {
// self.presentingViewController?.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
Screenshot of my storyboard
Well I thought I post the answer incase this happens to anyone else.
In ViewDidAppear, I was setting the searchController to active
searchController.isActive = true
Well before I dismissed, I needed to set it to inactive!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let value = tableView.cellForRow(at: indexPath)?.textLabel?.text {
self.searchController.searchBar.text = value
searchDestination = value
if !recentSearches.contains(value) {
recentSearches.append(value)
}
}
self.searchController.isActive = false
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
Don't perform segue, try to dismiss view controller directly and before dismissing set the property of presenting view controller to the search result
I have a table view inside a UIViewController with editing enabled for deleting rows. Swiping from the right lets me delete rows and I have the edit button on the navigation bar but it doesn't actually do anything except switch from saying Edit to Done.
Here is how I'm creating my table view.
override func viewWillAppear(animated: Bool) {
for view in self.view.subviews {
view.removeFromSuperview()
}
if sharedCart.shoppingCart.isEmpty {
self.navigationItem.rightBarButtonItem = nil
isEmptyLabel = UILabel(frame: CGRectMake(self.view.frame.width / 2, self.view.frame.height / 2, self.view.frame.width, self.view.frame.height))
isEmptyLabel.center = self.view.center
isEmptyLabel.text = "Your cart is empty."
isEmptyLabel.textAlignment = .Center
isEmptyLabel.textColor = UIColor.whiteColor()
isEmptyLabel.font = UIFont(name: "Helvetica-Light", size: 20.0)
self.view.addSubview(isEmptyLabel)
} else {
isEmptyLabel.removeFromSuperview()
total = 0
let editItem = self.editButtonItem()
self.navigationItem.rightBarButtonItem = editItem
tableView = UITableView(frame: self.view.frame, style: .Grouped)
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = UIColor.clearColor()
tableView.contentInset = UIEdgeInsetsMake(44, 0, 0, 0)
tableView.registerNib(UINib(nibName: "CartCell", bundle: nil), forCellReuseIdentifier: "passCartCell")
tableView.registerNib(UINib(nibName: "CartFooterView", bundle: nil), forHeaderFooterViewReuseIdentifier: "cartFooter")
self.view.addSubview(tableView)
}
And I use these methods for editing.
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
sharedCart.shoppingCart.removeAtIndex(indexPath.row)
self.viewWillAppear(true)
}
}
Cedric Michael's answer almost works, but it disables editButtonItem's automatic, animated toggling between the Edit and Done title & associated state. The better fix is this:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: animated)
}
Also, if you want to hide the Edit button for an empty shopping cart, it would be better form to set navigationItem.rightBarButtonItem = editButtonItem in viewDidLoad() and simply set navigationItem.rightBarButtonItem.isHidden to true or false in viewWillAppear() according to the shopping cart.
For Swift 3, implement this in your UIViewController class:
override func setEditing(_ editing: Bool, animated: Bool) {
if(editing && !tableView.isEditing){
tableView.setEditing(true, animated: true)
}else{
tableView.setEditing(false, animated: true)
}
}
(This is not necessary for a UITableViewController class).
You should also set your editButtonItem before:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = editButtonItem
}
Then it works! ツ
In order for your table to toggle between edit and normal mode on button tap you need to implement
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if self.tableView.editing {
return UITableViewCellEditingStyle.Delete
}
else{
return UITableViewCellEditingStyle.None
}
}
And on button tap or IBAction of button :)
#IBAction func editButtonTapped(sender: AnyObject) {
var button = sender as! UIButton
if button.titleLabel!.text! == "Edit" {
self.tableView.editing = true
button.setTitle("Done", forState: UIControlState.Normal)
}
else{
self.tableView.editing = false
button.setTitle("Edit", forState: UIControlState.Normal)
}
}
EDIT
Just realised you have bar button item on navigation bar rather then plain UIButton :)
So you can modify the IBAction as below :) Make sure the you select System Item for UIBarButton item as Custom and set the title as Edit
#IBAction func editButtonTapped(sender: AnyObject) {
let button = sender as! UIBarButtonItem
if button.title! == "Edit" {
self.tableView.editing = true
button.title = "Done"
}
else{
self.tableView.editing = false
button.title = "Edit"
}
}
This should do the job :) Happy coding :)
In my iOS 8 app I've got a custom ViewController which I present as Popover. This ViewController has a delegate, which get, and send to parent ViewController, the clicked index in popup. The problem is that I can't dismiss this Popover after selectRow.
Here's the code:
This is the method which I call when I want to show my Popup.
#IBAction func registerButtonAction(sender: UIButton) {
popup = self.storyboard!.instantiateViewControllerWithIdentifier("PopupViewController") as? PopupViewController
popup!.modalPresentationStyle = .Popover
popup!.preferredContentSize = CGSizeMake(100, 120)
let popoverMenuViewController = popup!.popoverPresentationController
popoverMenuViewController?.permittedArrowDirections = .Up
popoverMenuViewController?.delegate = self
popoverMenuViewController?.sourceView = sender
popoverMenuViewController?.sourceRect = CGRect(
x: sender.frame.size.width/2,
y: sender.frame.size.height/2,
width: 1,
height: 1)
popup!.delegate = self
presentViewController(
popup!,
animated: true,
completion: nil)
}
Here's PopupViewController code:
protocol PopupViewControllerDelegate
{
func rowClickedAtIndex(var index : Int)
}
class PopupViewController: MainViewController {
var delegate : PopupViewControllerDelegate?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension PopupViewController:UITableViewDelegate{
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell:PopupTableViewCell? = tableView.dequeueReusableCellWithIdentifier("PopupTableViewCell") as? PopupTableViewCell
if cell == nil {
cell = PopupTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "PopupTableViewCell")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.delegate != nil)
{
self.delegate!.rowClickedAtIndex(indexPath.row)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int{
return 2
}
}
Thank you.
I solved by myself:
I checked that the problem wasn't that the popover wasn't dismissed, but it was dismissed but after different seconds.
So I put my dismiss call in the main thread and it worked perfectly. Here's the code:
extension WelcomeViewController: PopupViewControllerDelegate {
func rowClickedAtIndex(index: Int) {
dispatch_async(dispatch_get_main_queue(),{
self.dismissViewControllerAnimated(true, completion: nil)
println(index)
})
}
}
I want to thank you Frankie, who helped me in finding the solution, removing what shouldn't be the problem.
Move your dismissViewControllerAnimated(true, completion: nil) call into the delegate at the end of the clickedRowAtIndex method. In other words, the presenting view controller should call the dismissing, not the presented view controller.
Try this:
extension WelcomeViewController: PopupViewControllerDelegate {
func rowClickedAtIndex(index: Int) {
dismissViewControllerAnimated(true, completion: nil)
println(index)
}
}