I am trying to attach data to a UITableView. I have download the project form here and am using the code where data is attached to the tableView: http://yannickloriot.com/2016/01/make-uitableview-reactive-with-rxswift/:
Firstly I have created the following variable:
let currentQuestion: Variable<Taxi?> = Variable(nil)
I then try to do the following:
currentQuestion
.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self)) { (row, element, cell) in
cell.choiceModel = element
}
.addDisposableTo(disposeBag)
But I am getting the following warning: 'Extra argument in call' on the line .bindTo. I have tried adding a new cell and get the same result. Not sure if it is relevant, but I have registered the cell.
I have read here that you can get this warning if the types of the arguments don't match: Swift - Extra Argument in call . However it looks like the arguments match fine.
I am new to Rx and was hope someone could help me understand what might be going wrong here. Thanks.
======
Edit
Here is my new code. I have tried rx_itemsWithCellIdentifier("ChoiceCell") alone and rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self):
let currentQuestion = Variable<[Taxi]>(taxis)
currentQuestion.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell")) {(row, element, cell) in
cell.choiceModel = element
}.addDisposableTo(disposeBag)
Where I have used (taxis), it is an array of taxi items. See picture below:
Also once I have called .asObservable(), I have the following:
I managed to print these out by removing the line .bindTo. If I add that line back I get the same error as before.
IMPORTANT: I played around with code base from article I linked to earlier. If I remove from ChoiceCell I can replicate the same error:
// var choiceModel: ChoiceModel? {
// didSet {
// layoutCell()
// }
// }
From experience the extra argument in call message is most often given when you are trying to bind a variable with the wrong expected data type. The first issue is that you are trying to bind a single instance of Taxi to the tableview which is expecting a sequence of observables.
/**
Binds sequences of elements to table view rows.
- parameter cellIdentifier: Identifier used to dequeue cells.
- parameter source: Observable sequence of items.
- parameter configureCell: Transform between sequence elements and view cells.
- parameter cellType: Type of table view cell.
- returns: Disposable object that can be used to unbind.
*/
public func rx_itemsWithCellIdentifier<S : SequenceType, Cell : UITableViewCell, O : ObservableType where O.E == S>(cellIdentifier: String, cellType: Cell.Type = default) -> (source: O) -> (configureCell: (Int, S.Generator.Element, Cell) -> Void) -> Disposable
It doesn't seem like the issue is caused by the optional object but I don't see why you would want to bind optional objects to the tableview, so I would advice you to avoid that too. Here is an example which would work.
let currentQuestion = Variable<[Taxi]>([Taxi()])
currentQuestion.asObservable().bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell")) {(row, element, cell) in
cell.choiceModel = element
}.addDisposableTo(disposeBag)
Thanks to Philip Laine answer above: https://stackoverflow.com/a/36536320/2126233
That helped me see that I was making a mistake in regards to what I was observing. It helped me to see the problem I was having in my code.
If you just want to bind to a normal tableViewCell then you need to use tableView.rx_itemsWithCellFactory:
currentQuestion.asObservable()
.bindTo(tableView.rx_itemsWithCellFactory) {(tableView, row, item) in
let cell = UITableViewCell()
cell.textLabel?.text = item.distance
return cell
}.addDisposableTo(disposeBag)
If you are using a custom cell then you can use tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self). Here is an example:
currentQuestion
.asObservable()
.filter { $0 != nil }
.map { $0!.choices }
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self)) { (row, element, cell) in
cell.choiceModel = element
}
.addDisposableTo(disposeBag)
For me I was still getting this same error unless I had a property inside the tableView cell that matched the element that was coming out of the array I was binding the tableView to.
So if you have an array of [Taxis] like so then inside the tableViewCell I required a variable that stored a Taxis. Then I was able to compile my project.
So in ChoiceCell I have a var like so:
var taxi: Taxi? {
didSet {
layoutCell()
}
}
I hope this helps anyone else having issues binding a tableViewCell to an array.
Well, this is a late answer but here is your problem. If this can help others to understand it.
You have an array of Taxi elements
when rx_itemsWithCellIdentifier() is called, element is a Taxi (of course, it is an element of the array)
But in your code you do:
cell.choiceModel = element
// Where
var choiceModel: ChoiceModel? {
didSet {
layoutCell()
}
}
So, you are trying to assign a Taxi to a ChoiceModel. This is why your are getting this error. Swift type inference determines that the types mismatch. You can try to comment the line inside the block and the error should disappear.
Related
I have created my own Struct object, that is populated from the Photos framework with each "moment" an image was contained in, and other metadata.
struct MomentImage {
var momentNo : Int
var index : Int
var image : UIImage
}
I would like to populate a tableView, split with:
Sections, which map to momentNo
Cells, which map to index
In tableView(cellForRowAt indexPath), I have printed out:
print("location: \(indexPath.section) and \(indexPath.row)")
But the Section value only ever prints "0". I expected my print statements to occur through all the sections in this table (with my test data, this is actually 13 sections)
How do I filter / retrieve this object at these 2 indices from my array of var moments = [MomentImage]...
Using array.filter { } seems the most obvious way. How do I pass in a 2nd property to check (i.e. momentNo AND index) within the same call?
let filteredassets = assets.filter { $0.momentNo == indexPath.section }
is my current code.
Note, the images need to be encodable, hence extracting from the PHImageManager into a distinct UIImage within a Struct that should itself be encodable.
As provided by #Vicaren, applying a && after the 1st parameter converts into to a 2-property IF AND statement
$0 is still available to select, simply choose another available property to test.
My own example results in:
let filteredassets = assets.filter { $0.momentNo == indexPath.section && $0.index == indexPath.row }
I have checked this SO answer to find a solution for the deprecated find method, but the solution is for single array. In my project I am have two arguments that are both collection types. Through refactoring I am receiving the familiar conditional binding errors.
I have tried removing the conditional binding and using index(of: ) as in the SO answer I referenced, but since I'm not working with single elements like String types naturally I get tuple errors.
If methods can't be called on tuples why was the original find method able to call the tuple in the Swift 2 line below?
class MoneyPickerTableViewController: UITableViewController {
var money: [String: String]
var purchaseOrder: [String]
var chosenKey: String = USDPreferences.shared().object(forKey: kUSDChosenMoneyKey) as! String
// Swift 2
if let index = find(purchaseOrder, chosenKey) {
let indexPath = NSIndexPath(forRow: index, inSection: 0)
tableView.selectRowAtIndexPath(indexPath, animated: animated, scrollPosition: .Middle)
}
navigationController?.setNavigationBarHidden(false, animated: animated)
}
Swift 3
let collections = (purchaseOrder, chosenKey) as ([String], String)
let index = collections.index(of: )
Your Swift3 attempt has nothing to do with your Swift2 code.
In the Swift2 code, find(purchaseOrder, chosenKey) returns the index of the String variable, chosenKey in an array of Strings, purchaseOrder. In your Swift3 code you are trying make a tuple from your array of strings and from your search string and look up the elements in a tuple, which will never work, since you are trying to search a tuple with a non-tuple value.
In reality the code should be this simple: you just need to find the index of chosenKey in purchaseOrder using the index(of:) function and use that as the row index.
guard let row = purchaseOrder.index(of: chosenKey) else { return }
let indexPath = IndexPath(row: row, section: 0)
tableView.selectRow(at: indexPath, animated: animated, scrollPosition: .middle)
find(_:_:) used to be a free function that took a Collection as its first argument, and the desired element as the second.
Since Swift 2, this functionality is now implemented as an instance method on Collection, called index(of:).
What used to be find(purchaseOrder, chosenKey) is now spelled purchaseOrder.index(of: chosenKey)
Edit: I figured out how to prevent this, but I still have a question as to why it returns an optional value. You may skip to the end.
I'm quite new to Swift, and I'm getting behavior I can't understand. Let's say I drag a popup button called myButton into the ViewController. I want to print the currently selected item to the console. Here's how I would do it:
#IBOutlet weak var myButton: NSPopUpButton!
override func viewDidLoad() {
super.viewDidLoad()
let myVariable = myButton.titleOfSelectedItem
print(myVariable)
// Do any additional setup after loading the view.
}
I expect Item1 to be printed, as that's the option selected by default when the view loads. However, I actually get Optional("Item 1").
This has been causing a few problems for me. For example, the print(myVariable) line gives me this cryptic error:
Expression explicitly coerced from 'String?' to Any
Also, I can't do something like this:
if myButton.titleOfSelectedItem == "Item1" || "Item3" {
let currentSelection = "odd"
} else {
let currentSelection = "even"
}
as I get a bunch of errors – because of the || and the else as far as I can tell, even though I think it should work fine.
I've tried searching for why this occurs, but couldn't find any explanations. From the warning, it seems like when I get the selection with titleOfSelectedItem, it gives me an optional value. This doesn't make sense, as a menu item has to be selected. Its value can't be nil.
I looked up a bunch of tutorials to see how they implement popup button functionality. The only thing I could see was when someone
Made an array
Removed all items from the popup button with func removeAllItems()
Added items to the popup button from the array with func addItems(withTitles: [String])
Obtained the index of the selected item with var indexOfSelectedItem: Int
Retrieved the respective value from the array
and then used that. This, however, seems unnecessarily complicated and I can't understand why I wouldn't be able to get just the title of the selected popup item simply with myButton.titleOfSelectedItem. Can anyone suggest to me what to do?
Edit:
So I realized you need to "unwrap" the optional value to make it a normal value, which is as simple as adding a ! to the end of the variable, as follows:
#IBOutlet weak var myButton: NSPopUpButton!
override func viewDidLoad() {
super.viewDidLoad()
let myVariable = myButton.titleOfSelectedItem!
print(myVariable)
// Do any additional setup after loading the view.
}
Now there's no error, and Item1 is printed.
What I can't yet understand is, why was an optional value printed in the first place? There are three items in the NSPopUpButton. Any one of the three has to be selected. There's no opportunity for myButton.titleOfSelectedButton to be nil. Why then do I need to unwrap it to use it as a string with myButton.titleOfSelectedButton! if it's not optional?
titleOfSelectedItem returns an optional because no item could be selected.
You need optional bindings to unwrap the optional safely and you have to evaluate both "Item1" and "Item3" against the title string:
if let title = myButton.titleOfSelectedItem {
print(title)
let currentSelection : String
if title == "Item1" || title == "Item3" {
currentSelection = "odd"
} else {
currentSelection = "even"
}
}
In Objective C you can do it in awakeFromNib:
[[your_NSView your_NSPopUpButton] selectItemAtIndex:your_Int];
... of course, you must have declared 'your_NSView' and 'your_NSPopUpButton' as properties.
Confused as to why getting error "Cannot subscript a value of type 'inout [[String]]' (aka 'inout Array>'). Within a working table view class
(Originally followed Jared Davidson tutorial https://www.youtube.com/watch?v=pR6dR-vVZeY)
var secondArray = [SecondTable]()
let latest = ViewController().getArrayListLast()
var latestClass = latest.getFullClasses()
print(latestClass[0])
for i in 0...(latest.getAssetClasses().count)
{
if SecondTable(secondTitle: latestClass[i]) != nil
{
secondArray = secondArray.append(SecondTable(secondTitle: latestClass[i]))
}
}
append(_:) is mutating function and it doesn't return anything.
Now your code making 3 more mistakes.
As #vadian mentioned in comment ViewController() will never work when using storyboard.
It will give you run time crash index out of bounds because you have provide for loop till the array count but it should be till the less one of the array count, so it should like for i in 0..<(latest.getAssetClasses().count)
You are creating two times object with init of SecondTable to just check for the nil instead of that you can use if let.
if let secondTable = SecondTable(secondTitle: latestClass[i]) {
secondArray.append(secondTable)
}
I have an array of objects which all have a timeStamp (NSDate) and I am trying to sort them with the closure below.
The problem is that this works well without any optimisation, so with debug builds all is fine and dandy. But in with optimisations I get the following EXC_BAD_ACCESS.
reportCells.sort({ (a: UITableViewCell, b: UITableViewCell) -> Bool in
if let first = a as? GreenelyTableViewCell,
second = b as? GreenelyTableViewCell {
if let firstDate = first.timeStamp,
secondDate = second.timeStamp {
let comparison = firstDate.compare(secondDate)
if comparison == NSComparisonResult.OrderedAscending {
return false
} else {
return true
}
}
}
return false
})
Any workarounds?
The issue is you are sorting the cells. Which is not a proper approach first of all. You should sort your data source array. Because table is managing its cells in memory and when you make an array of cells. It will cause issues in backend even if it seems working. Now coming to your problem,
You are sorting cells and when you get back to the tableview listing methods, it agains fetches the data from data source i mean your array of objects. And tableview don't finds proper arrangements of data. I mean your cells are in 1 , 4, 2, 3 order after your sort while your data is stil in 1,2,3,4 order. This will cause table view to throw exception. More over, cells resulebility will cause also issue. So for your is to sort your data before showing it to cell. And I bet your problem will be resolved.