I created a Custom Cell to replace UITableViewSectionHeader with a protocol to process when UISegmentedControl.index is changed so i can order the table by name or value:
import UIKit
protocol OrdenarTableViewDelegate {
func ordenarTableView(cell: OrdenarTableViewCell)
}
class OrdenarTableViewCell: UITableViewCell {
var delegate: OrdenarTableViewDelegate?
#IBOutlet weak var segmentedOrdenar: UISegmentedControl!
#IBAction func alteraOrdenacao(sender: UISegmentedControl) {
delegate?.ordenarTableView(self)
}
On the UITableViewControler I referenced the protocol
class SelecionadasTableViewController: UITableViewController, UITextFieldDelegate, OrdenarTableViewDelegate {
and implemented it:
// MARK: - Ordenar Delegate
func ordenarTableView(cell: OrdenarTableViewCell) {
if cell.segmentedOrdenar.selectedSegmentIndex == 0 {
listaCervejas = bancoDeDados.selecionaCervejas(false)
} else {
listaCervejas = bancoDeDados.selecionaCervejas(true)
}
ordenarCervejas = cell.segmentedOrdenar.selectedSegmentIndex
tableView.reloadData()
}
When I select the index 1, the app works just fine, but when index 0 is selected the action is only performed if click on a UITextField on any other cell or I pull the table down for refreshing (which by the way is not implemented to refresh)
Is there anything that I'm missing? Because it just feels really awkward.
I notice that the problem only occurs when I click one option and then the other on the UISegmentedControl without touching nowhere else on the screen...
U can created a Custom View to replace tableHeaderView, try it.
When you reload the tableview, everything added to the table view will be reloaded. Check the selected segment index every time and reset this after table reloading.
I suspect that the update of the cell is not executed. Is the reloadTable executed on the mainThread?
Include a print log statement in CellForRowAtIndexPath to find out.
I actually found the solution. You can drag the IBAction to the tableViewController instead. Then it works just fine!!
Thank you all for the help.
Related
It can sounds weird but I don't understand why my tableView is showing cells.
I got array of items that should be shown in cells but I don't run reloadData method of my tableView anywhere in my code. It seems that some of app components or maybe frameworks inside app is calling reloadData method and I want to find out which one?
How it can be done?
A table view loads itself the first time it is added to the window hierarchy. You don't need an explicit call to reloadData for the table to load itself initially.
If you want to see how this is really done, put a breakpoint on your table view data source methods and bring up your table view. Look at the stack trace in the debugger to see the sequence of events.
If your data preparation takes some time and you do not want the table view to show any data initially you could use an approach like this:
class TableViewController: UITableViewController {
var someDataSource: [Any]!
var dataSourcePrepared = false {
didSet {
tableView.reloadData()
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
guard dataSourcePrepared else { return 0 }
return someDataSource.count
}
func doSomePreparationStuff() {
// ...
// ...
someDataSource = ["Some", "Content"]
dataSourcePrepared = true
}
}
In this case I used a Bool variable dataSourcePrepared which is false initially. As soon as you have prepared your content set it to true and the table view gets reloaded.
I searched a lot through Google and SO, so please forgive me, if this question has already been answered!
The problem:
I have a UICollectionView with n UICollectionViewCells. Each cell contains a UIView from a XIB file. The Views are used for data entry, so all cells have a unique reuseIdentifier. Each View has also a unique restorationIdentifier. Everything works in normal usage, but not when it comes to state restoration:
The first 3 or 4 cells are getting restored properly because they are visible on the screen on startup, but the remaining cells, which are not visble, are not getting restored.
Current solution:
So I've discovered so far that a View is only restored if it's added to userinterface at startup.
My current working solution is to set the height of all cells to 1 in the process of restoring. Now every cell is loaded and all views are restored.
When applicationFinishedRestoringState() is called, I reload the CollectionView with the correct height.
Now my question is: I'm not happy with this solution, is there a more clean way to achieve restoring of all the UIViews?
I think you are getting a bit confused between your data model and your views. When first initialised, your table view is constructed from a data model, pulling in stored values in order to populate whatever is in each cell. However, your user does not interact directly with the data model, but with the view on the screen. If the user changes something in the table view, you need to signal that change back up to the view controller so that it can record the change to the data model. This means in turn that if the view needs to be recreated the view controller has the information it needs to rebuild whatever was in the table when your app entered the background.
I have put together a simple gitHub repository here: https://github.com/mpj-chandler/StateManagementDemo
This comprises a CustomTableViewController class which manages a standard UITableView populated with CustomTableViewCells. The custom cells contain three switch buttons, allowing the state of each cell to be represented by an array of Boolean values.
I created a delegate protocol for the cells such that if any of the switches is tripped, a signal is sent back to the view controller:
protocol CustomTableViewCellDelegate {
func stateDidChange(sender: CustomTableViewCell) -> Void
}
// Code in CustomTableViewCell.swift:
#objc fileprivate func switched(sender: UISwitch) -> Void {
guard let index : Int = switches.index(of: sender) else { return }
state[index] = sender.isOn
}
// The cell's state is an observed parameter with the following didSet method:
fileprivate var state : [Bool] = Array(repeating: false, count: 3) {
didSet {
if state != oldValue, let _ = delegate {
delegate!.stateDidChange(sender: self)
}
}
}
CustomTableViewController is registered to the CustomTableViewCellDelegate protocol, so that it can record the change in the model as follows:
// Code in CustomTableViewController.swift
//# MARK:- CustomTableViewCellDelegate methods
internal func stateDidChange(sender: CustomTableViewCell) -> Void {
guard let indexPath : IndexPath = tableView.indexPath(for: sender) else { return }
guard indexPath.row < model.count else { print("Error in \(#function) - cell index larger than model size!") ; return }
print("CHANGING MODEL ROW [\(indexPath.row)] TO: \(sender.getState())")
model[indexPath.row] = sender.getState()
}
You can see here that the function is set up to output model changes to the console.
If you run the project in simulator and exit to the home screen and go back again you will see the state of the tableView cells is preserved, because the model reflects the changes that were made before the app entered the background.
Hope that helps.
I have a custom UIPickerView class (also conforms to UIPickerViewDelegate, UIPickerViewDataSource) that is used in 2 view controllers.
So in both of them I have this:
myPickerView.delegate = myPickerView and same with dataSource.
All the rows and components are configured in that custom picker view class.
I want to disable a button in my VCs when the 1st row is selected.
Usually I would do this by using one of the picker view's delegate functions (pickerView(_:,didSelectRow:, inComponent:), but since I use a custom one that already conforms to UIPickerViewDelegate, I can't do that.
Hmm, tricky. The issue is that the picker is its own delegate, which makes the solution a bit complicated, but here's what I might do: give the picker a closure that gets called when the selected row changes. Then call that closure from the delegate method.
class MyPickerView {
typealias SelectedIndexClosure = (Int) -> Void
var selectedIndexClosure: SelectedIndexClosure = { _ in }
...
func picker(_ picker: UIPickerView, didSelectRow row: Int ...) {
selectedIndexClosure(row)
}
}
Then you'd set the selectedIndexClosure to whatever code you want to be run in the view controller.
myPickerView.selectedIndexClosure = { [weak self] index in
self?.button.enabled = (index == 1)
}
And that should do. An more idiomatic solution might be to refactor the picker's datasource and delegate methods into a new, separate object owned by the view controller, but this should work fine.
You can say self.button.isEnabled = self.picker.selectedRow(inComponent: 0) == 0 if you just need to set it once. If you need it to be reenabled after moving the picker, or disabled if you move back to the first row, you will need to use that same delegate method.
Anyone know why can't I access the variable 'sendTitle' in prepareForSegue from didSelectRowAtIndexPath? I saw some of the tutorials, they are doing the same thing with me, I've no idea why mine got error.
--What I'm doing now is passing value from tableView cell to another view controller.
I'm using Xcode 7 Swift 2.1
you have to declare sendTitle inside your class to make it visible to all methods. Something like follows.
import UIKit
class viewController: UITableViewController {
var sendTitle:String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
//table view delegate methods
// just use sendTitle = cell.lblTitle.text!
}
Because it is declared privately in your didSelectRowAtIndexPath method. It won't be accessible outside of those function. if you want to access it from another function, declare it as global variable in your view controller.
I have a UIView with a TableView and a Button (Big Button). The TableView has a custom Cell. In this cell there is an "Add" button. I want to animate the first button when the user makes click on the Add button.
This is my schema:
This is my code:
class ProductsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var bigButton: UIButton! <- I WANT TO ANIMATE THAT BUTTON
}
ProductCell class
class ProductCell: UITableViewCell {
#IBAction func addProduct(sender: AnyObject) {
//I WANT TO ACCESS THE BIG BUTTON FROM HERE
}
}
Screen example of my app
I've tried to get the parent controller or the superview to get the IBOutlet but the app is crashing allways
Add block properties to your cells which lets them notify your view controller when they have been clicked. In your view controller block code, you can then access the big button.
See my answer to a similar question. Simply replace the switch example with your button. So replace UISwitch with UIButton.
How can I get index path of cell on switch change event in section based table view
So rather than have the cell try and talk to another cell/button, have the cell notify the controller which can then manage the big button changes.
Although I made a comment about using alternate methods you could also employ a strategy below based on updates to a property stored in the current view controller class. You could just as well use property observation on the ProductsViewController but I assume you'd like to keep OOP focused and reduce the size of your controller.
Subclass the ViewController
One could subclass an existing UIViewController and then create a property in the super class that deals with the value that was changed (row tapped). In that subclass you could then do some animation. Because you would be subclassing you continue to obtain all the benefits and methods defined in your existing controller. In your identity inspector point your Class to the new subclass and create any functional updates to your UI using animation.
class ProductsViewController:... {
var inheritedProperty:UIView = targetView {
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
}
}
class AnimatedProductsViewController:ProductsViewController {
override var inheritedProperty:UIView {
//do something interesting if the property of super class changed
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
//you might want to call this method like so
// didSet { animate(newValue) }
}
func animate (view: UIView){
//do animation routine using UIView animation, UIDynamics, etc.
}
}
Property Observation
Whenever the didSelectCell... method is called just set a value to the inheritedProperty. Then add the property observers (see sample code) and react when the property changes (maybe pass a reference to the view you want to animate).
For example: Within the property observer you can just take that view and pass it to your animator function (whatever is going to do the animation). There are many examples on SO of how to animate a view so just search for (UIView animation, UIDynamics, etc).
The normal benefits of separation are encapsulation of functionality and reuse but Swift also guarantees that each set of property observers will fire independently. You'd have to give some more thought to this as to its applicability in this use case.
Do all this things in your viewController
Add target Method to cell's add button in cellForRowAtIndexPath Method
Like This
cell.add.addTarget(self, action: "addProduct:", forControlEvents: UIControlEvents.TouchUpInside)
Define method
func addProduct(button:UIButton)
{
// do button animation here
}