RxSwift - optional issue on binding to tableView - ios

I have an UITableView and a users variable whose signature is like:
let users: Variable<[User]?> = Variable<[User]?>(nil)
While I'm trying to bind this array of users in a UITableView, I'm getting the error Generic parameter 'Self' could not be inferred. This only occurs when I'm observing an Optional type. Why does this happen? What's the workaround for this situation?
Here is an example of how I'm doing:
private func bind() {
// Does not compile: Generic parameter 'Self' could not be inferred
users.asObservable().bind(to:
tableView.rx.items(cellIdentifier: "UserCell",
cellType: UITableViewCell.self)) { (index, user, cell) in
// Cell setup.
}.disposed(by: disposeBag)
}

You see this error because the compiler cannot infer the type of the Optional variable users passed into the following functions which work on generics and need to be able to infer the type.
An Optional in Swift is actually implemented as shown below. I guess in case of an Optional being potentially nil aka .none the compiler cannot infer the type for methods like e.g. items and bind(to:) which work on generics.
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
/// Creates an instance that stores the given value.
public init(_ some: Wrapped)
//...
}
Workaround 1.): You could just use filterNil() (RxOptionals lib) to avoid the problem.
private func bind() {
users.asObservable().filterNil().bind(to:
tableView.rx.items(cellIdentifier: "UserCell",
cellType: UITableViewCell.self)) { (index, user, cell) in
// Cell setup.
}.disposed(by: disposeBag)
}
Workaround 2.): Make users non-optional. If you have no users just set an empty array as value.
let users: Variable<[User]> = Variable<[User]>([])
Workaround 3.): Use Nil-Coalescing Operator ?? in map function like
private func bind() {
users.asObservable().map { optionalUsers -> [User] in
return optionalUsers ?? []
}
.bind(to:
tableView.rx.items(cellIdentifier: "UserCell",
cellType: UITableViewCell.self)) { (index, user, cell) in
// Cell setup.
}.disposed(by: disposeBag)
}
Sidenote: Variable is deprecated in the latest version of RxSwift
Just for reference:
Implementation of items
public func items<S: Sequence, Cell: UITableViewCell, O : ObservableType>
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
-> (_ source: O)
-> (_ configureCell: #escaping (Int, S.Iterator.Element, Cell) -> Void)
-> Disposable
where O.E == S {
return { source in
return { configureCell in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
let indexPath = IndexPath(item: i, section: 0)
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
return self.items(dataSource: dataSource)(source)
}
}
}
Implementation of bind(to:)
public func bind<O: ObserverType>(to observer: O) -> Disposable where O.E == E {
return self.subscribe(observer)
}

I had the same error without an optional type. It happened because I was using a UICollectionViewCell subclass in a UITableView.

Related

cannot able to make struct variable as mutable type

I am facing the issue of "Cannot assign to immutable expression of type 'Bool'" . Please look at the below code. I am getting error in viewForHeaderInSection. Actually where should i do modification to make it work?.
struct VenueDetail {
var isVeg: Bool
}
struct VenueDetailDTOMapper {
static func map(_ dto: DetailDataDTO) -> VenueDetail {
return VenueDetail(isVeg: dto.isVeg)
}
}
In API Manager I have get the data from api and use above struct as follow
let venueDetail = VenueDetailDTOMapper.map(getDetail)
ViewModel:
enum VenueDetailVMTypes {
case veueInfoInfo
}
protocol VenueDetailVMItems {
var type: VenueDetailVMTypes { get }
}
struct VenueInfoViewModel: VenueDetailVMItems {
var type: VenueDetailVMTypes {
return .veueInfoInfo
}
var headerSection: VenueDetail
}
func cretaDataSource() {
if let getVenueDetails = self.venueDetails {
let vmType = VenueInfoViewModel(headerSection: getVenueDetails)
arrayDataSource.append(vmType)
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
let venueDetailVMItems = viewModel.arrayDataSource[section]
switch venueDetailVMItems.type {
case .veueInfoInfo:
let headerCell = tableView.dequeueReusableCell(withIdentifier: kCellIdentifierVenueHeader) as! VenueHeaderTVCell
headerCell.updateCellData(detail: (venueDetailVMItems as! VenueInfoViewModel).headerSection)
headerCell.foodTypeHandler = { [weak self] (isOn) in
guard let strongSelf = self else {
return
}
strongSelf.viewModel.showOnlyVegMenu(shouldShowVeg: isOn)
(venueDetailVMItems as! VenueInfoViewModel).headerSection.isVeg = isOn. //Cannot assign to immutable expression of type 'Bool'
strongSelf.tableView.reloadData()
}
headerView.addSubview(headerCell)
break
}
return headerView
}
Structs are value types, so each time you assign a struct, it makes a copy. You're treating it as a reference type. Stripping away all the as! casting, what you've done is:
let value = array[index]
value.someBool = true
reloadData()
Even if value were mutable (which it could be), that wouldn't do anything. value is a copy of array[index], not a reference to it. If you want it to be a reference, then you need to make it a reference type (a class).
You've used a protocol and a "type" identifier, where what I think you really wanted was an enum with associated data:
enum VenueDetail {
case veueInfoInfo(VenueInfoViewModel)
}
With this, you get rid of all of the dangerous and complicated as! casting.
But all of that doesn't really change the issue you're describing. Either way (with a protocol or with an enum), what you need to do is:
var value = array[index]
// change value the ways you want; set the bool, etc.
array[index] = value
A structure is an aggregation of fields; if a particular structure instance is mutable, its fields will be mutable; if an instance is immutable, its fields will be immutable. A structure type must thus be prepared for the possibility that the fields of any particular instance may be mutable or immutable.
Please check this
So try to change let to be var
Make sure the the arrayDataSource is mutable user var not let
var arrayDataSource = [VenueInfoViewModel]()
After struggling i just create a method in viewModel that removes objects in array of type .venueInfo and reload, i know its kind of hack but time being i have no option. In case if somebody found better way, really appreciated
func changeHeaderSwitch(isVeg: Bool) {
arrayDataSource.removeAll { (venueDetailVMItems) -> Bool in
return venueDetailVMItems.type == .veueInfoInfo
}
if var getVenueDetails = self.venueDetails {
getVenueDetails.isVeg = isVeg
let vmType = VenueInfoViewModel(headerSection: getVenueDetails, arrayMenuInfo: [])
arrayDataSource.append(vmType)
}
}

Get model from UICollectionView indexpath

I'm using RxSwift to bind a model array to a collection view
How do I get the model object from a given indexPath?
I'm doing the binding like this:
vm.bikeIssueCatagories()
.drive(self.collectionView.rx.items(cellIdentifier: "BikeIssueCategoryCollectionViewCell", cellType: UICollectionViewCell.self)) { row, data, cell in
}.disposed(by: disposeBag)
The core of my issue is, that I need to get both the model object and the cell that a user selects. Using collectionView.rx.modelSelected(T.self) only gives me the model og type T. And calling collectionView.rx.itemSelected only gives me the selected IndexPath
collectionView.rx.itemSelected.asDriver()
.driveNext { [unowned self] indexPath in
guard let model = try? collectionView.rx.model(at: indexPath) else { return }
guard let cell = self.collectionView.cellForItem(at: indexPath) else { return }
}.disposed(by: disposeBag)
But this gives me an error when trying to the the model at indexPath:
Type 'inout UICollectionView' does not conform to protocol
'ReactiveCompatible'
Just trying:
let indexPath = IndexPath.init()
self.collectionView.rx.model(at: indexPath)
also gives me an error:
Ambiguous reference to member 'model(at:)'
SO... How to get both the model object and the cell that a user selects?
I could have done like RamwiseMatt proposed. Making a method on my ViewModel that takes the IndexPath and return the model. I did however end up using a zip:
let modelSelected = collectionView.rx.modelSelected(SelectableBikeIssueCategory.self).asObservable()
let cellSelected = collectionView.rx.itemSelected.asObservable()
.map { [weak self] indexPath -> UICollectionViewCell in
guard let cell = self?.collectionView.cellForItem(at: indexPath) else { fatalError("Expected cell at indexpath") }
return cell
}
Observable.zip(cellSelected, modelSelected)
.map { [weak self] cell, category -> SomeThing in
return SomeThing()
}
Your self-accepted solution is not optimal, because the modelSelected stream maps indexPath internally and needs to be used when there is no need to know about indexPath. In your case, it is better to use use itemSelected and convert to a tuple for example.
collectionView.rx.itemSelected
.map { [weak self] indexPath in
guard
let model = try? self?.collectionView.rx.model(at: indexPath) as YourModelName?,
let cell = self?.collectionView.cellForItem(at: indexPath)
else {
preconditionFailure("Describing of error")
}
return (cell, model)
}
Your ViewModel defines a method bikeIssueCatagories(), which is what your UIViewController binds the UICollectionView to. To get your model at the correct position, you can use the itemSelected property you mentioned, which gives you an Observable<IndexPath>, which you should feed into your ViewModel. There, you can use the IndexPath's item property to determine which model from your data array (given in bikeIssueCatagories()) is the one you are looking for. The modelSelected property makes this easier for you since it already knows the datasource, so all you have to provide is the type of your Model (replace T.self with YourModelName.self.)
I'm not sure why you want a reference to your cell as well, but if you must, you can just use cellForItem(at:) on your UICollectionView (feeding it the IndexPath you obtained via itemSelected.)

Handling two different value types in swift

enum Sections: Int {
case parent
case general
}
struct Parent {
let name: String
}
enum General: Int {
case manage
case settings
func title() -> String {
switch self {
case .manage:
return "Manage"
case .settings:
return "Settings"
}
}
}
struct DataProvider {
func data(at index: NSIndexPath) -> ? {
let section = Sections(rawValue: index.section)!
switch section {
case .parent:
print("parent \(Parent(name: "Venkat"))")
return Parent(name: "Venkat")
case .general:
let general = General(rawValue: index.row)!
print(general.title())
return general
}
}
}
Here, func data(at index: NSIndexPath) needs to return value type based on indexpath. I tried with protocol but it need property requirement to handle in cell level. Any other way to implement the method and also "General" enum implementation
You could make a shared parent/protocol, then the function could return a shared parent instance, which you can then conditionally down cast. Or you could have the function return AnyObject which is a shared parent.
func data(atIndex: NSIndexPath) -> AnyObject {/*implementation*/}
/*
* Later in another function
*/
let someObj = data(atIndex:index)
if let parentObj = someObj as? Parent
{
// Do what you need with the parent object, possibly save it to a parent ref
}
And you can do that similarly for the General type. This is not a super scalable system because if you have 3-4 more types in a function that you want to return it gets messy with checking which type it is.
At that point though you would probably want to redesign your code, the return type of function should be constant whenever possible.

Swift generic closure

I'm building a library for static table views and it works fine, but I encountered a problem with generic closures.
It looks like this so far:
orderForm = Form(tableView: orderTable) { f in
f.section { s in
s.footer("Při platbě nejsou účtovány žádné další poplatky.")
s.cell("Selection")
.configure { (cell, path) in
let c = cell as! ProfileSelectionCell
c.titleLabel?.text = "Způsob platby"
c.detailLabel?.text = self.paymentType.first
}
s.cell("Selection")
.configure { (cell, path) in
let c = cell as! ProfileSelectionCell
c.titleLabel?.text = "Balíček"
c.detailLabel?.text = "20 kr. za 1000 Kc"
}.selected { path in
}
}
}
I wanna have the "cell" variable already cast to appropriate type, in this case ProfileSelectionCell.
Here is the source for the cell class:
class Cell {
internal let id: String
internal var configure: ((cell: UITableViewCell, path: NSIndexPath) -> Void)?
internal var selected: ((path: NSIndexPath) -> Void)?
init(id: String) {
self.id = id
}
func configure(config: ((cell: UITableViewCell, path: NSIndexPath) -> Void)?) -> Self {
self.configure = config
return self
}
func selected(selected: (path: NSIndexPath) -> Void) -> Self {
self.selected = selected
return self
}}
My problem is that if I make the configure method generic, it is not possible to store the config closure to the cell variable and if I make the whole cell generic, I can't save the cell to an array in Section class and so on..
Is this solvable in any way?
You can make the Cell class generic, e.g.
class Cell<T : UITableViewCell> {
}
and then use T instead of every UITableViewCell.
Unfortunately you would have to have the same in both Section and Form classes, too. That would work for tables with one type of cells but it won't probably work for tables with multiple cell types. In that case you will always need casting somewhere.

[AnyObject]?' does not have a member named 'subscript'

I'm loading a list of objects from a core data database into a table view.
class ScheduleViewController: UITableViewController {
private var items: [AnyObject]?
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let itemCount = items?.count {
return itemCount
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DayScheduleCell", forIndexPath: indexPath) as DayScheduleCell
if let act = items[indexPath.row] as Activity {
if act.client != nil {
// ...
}
}
return cell
}
}
The data is retrieved inside a closure so I have declared an items array as an optional because it might be nil in the first run.
I'm getting the error '[AnyObject]?' does not have a member named 'subscript' at this line if let act = items[indexPath.row] as? Activity.
I can't figure out how to resolve this.
The array is declared as:
private var items: [AnyObject]?
so, as you also said, it's an optional
In swift an optional is an enum, so a type on its own - and as an optional, it can contain either a nil value or an object of the contained type.
You want to apply the subscript to the array, not to the optional, so before using it you have to unwrap the array from the optional
items?[indexPath.row]
but that's not all - you also have to use the conditional downcast:
as? Activity
because the previous expression can evaluate to nil
So the correct way to write the if statement is
if let act = items?[indexPath.row] as? Activity {
First of all you need to unwrap or optional chain the items as it can be nil. AnyObject has different behaviour when getting as element from array, due to the fact that AnyObject can be a function. You would have to cast from items like this:
if let act = items?[indexPath.row] as AnyObject? as Activity? {
if act.client != nil {
// ...
}
}
If items will never contain a function you can use
private var items: [Any]?
instead and cast with:
if let act = items?[indexPath.row] as? Activity {
if act.client != nil {
// ...
}
}
I have fixed my problem by convert index var type from UInt to Int
let obj = items[Int(index)]
Hope this can help someone who still get this problem.

Resources