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.
Related
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)
}
}
After reading the Swift documentation and various online tutorials, it still a little hard to wrap my head around reference types versus value types.
I'm following a tutorial from a Swift TDD book to understand OOP architecture in Swift. The tutorial is based on creating a to do list app. In the beginning of the book we created a struct to resemble each to do item. Next we created an class called ItemManger to manage the array that holds to do items and another array to hold checked off to do items. I can understand the idea of to do items being made from a struct because its a value type which creates a new instance every time its instantiated and the itemManager being made from a class since we only need one itemManger to keep track of to do items. The question that I've come up with is when we create an instance of the ItemManager type (which is a class) inside another class or view controller, will this refer to the same class we created before in which we'll be able to access the to do item arrays?
Before this book, I assumed in order to keep track of variables from a class, we have to mark them as static.
Here is the itemManager class:
import Foundation
class ItemManager {
var toDoCount: Int {return toDoItems.count }
var doneCount: Int {return doneItems.count }
private var toDoItems: [ToDoItem] = []
private var doneItems: [ToDoItem] = []
func add(item: ToDoItem) {
if !toDoItems.contains(item){
toDoItems.append(item)
}
}
func checkItem(at index: Int) {
let item = toDoItems.remove(at: index)
doneItems.append(item)
}
func doneItem(at index: Int) -> ToDoItem{
return doneItems[index]
}
func item(at index: Int) -> ToDoItem{
return toDoItems[index]
}
func removeAll(){
toDoItems.removeAll()
doneItems.removeAll()
}
}
Here is another class where we create an instance variable of the ItemManager type:
import UIKit
enum Section: Int {
case toDo
case done
}
class ItemListDataProvider: NSObject, UITableViewDataSource, UITableViewDelegate {
var itemManager: ItemManager?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let itemManager = itemManager else{return 0}
guard let itemSection = Section(rawValue: section)else{ fatalError() }
let numberOfRows: Int
switch itemSection {
case .toDo:
numberOfRows = itemManager.toDoCount
case .done:
numberOfRows = itemManager.doneCount
}
return numberOfRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
guard let itemManager = itemManager else { fatalError() }
guard let section = Section(rawValue: indexPath.section) else { fatalError() }
let item: ToDoItem
switch section {
case .toDo:
item = itemManager.item(at: indexPath.row)
case .done:
item = itemManager.doneItem(at: indexPath.row)
}
cell.configCell(with: item)
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
}
In the code you posted you are not creating an instance of ItemManager.
Here is another class where we create an instance variable of the ItemManager type:
ItemListDataProvider can have a ItemManager but it does not create one. Creating an instance of a class works by calling it's constructor like this:
// Creates an instance of ItemManager and assigns it to itemManager
let itemManager = ItemManager()
Because you did not show where your item manager is created, the question
will this refer to the same class we created before in which we'll be able to access the to do item arrays?
can not really be answered. Where did you create an instance of ItemManager and what did you do with it?
Here is an example:
let itemManagerA = ItemManager()
let itemListDataProviderA() = ItemListDataProvider()
itemListDataProviderA.itemManager = itemManagerA
let itemListDataProviderB() = ItemListDataProvider()
itemListDataProviderB.itemManager = itemManagerA
In this example both ItemListProviders have the same ItemManager and thus have access to the same item arrays.
On the contrary if you are doing something like this:
let itemManagerA = ItemManager()
let itemListDataProviderA() = ItemListDataProvider()
itemListDataProviderA.itemManager = itemManagerA
let itemManagerB = ItemManager() // <-- This creates a SECOND instance of ItemManager
let itemListDataProviderB() = ItemListDataProvider()
itemListDataProviderB.itemManager = itemManagerB // <-- We use the SECOND instance instead of the first one for itemListDataProviderB
both ItemListProviders have different instances of ItemListProvider and do not have access to the same items.
I am new in Swift and don't have much more idea on optional (! , ?). I tried to fetch data from plist, create Model and show to UITableView. Table data shows perfectly, but it shows with Optional() binding. I tried change ! to ? but unable to unwrap. Could you please, guide me to solve this problem.
Here is my code & output -
var fileName : String?
var dataArray : Array<SHQuesAns>?
For fetch data from pList -
func loadTableView(){
dataArray = SHDataAccess.init(fname: fileName).arrayFromPlist()
self.questionTableView.dataSource = self
self.questionTableView.delegate=self
self.questionTableView.reloadData()
}
SHDataAccess class -
import UIKit
var fileName : String!
class SHDataAccess: NSObject {
init(fname:String?) {
super.init()
fileName = fname
}
func arrayFromPlist() -> Array <SHQuesAns>?{
let dataPlists = NSMutableArray(contentsOfFile:NSBundle.mainBundle().pathForResource(fileName, ofType: "plist")!)
var dataObj : Array <SHQuesAns>? = Array()
for data in dataPlists! {
dataObj?.append(SHQuesAns.init(_dic: data as! NSDictionary))
}
return dataObj
}
}
And UITableView delegates -
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray == nil ? 0 : dataArray!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let aCell = self.questionTableView.dequeueReusableCellWithIdentifier("qcell",forIndexPath: indexPath) as! SHQuestionCell
let q : SHQuesAns = dataArray![indexPath.row]
aCell.lblQuestion.text = "\(q.question)"
return aCell
}
Here is the output -
This will remove that Optional() text:
if let q = q.question as? String {
aCell.lblQuestion.text = "\(q.question!)"
} else {
aCell.lblQuestion.text = ""
}
The key is to unwrap the string contained in the question object so that when it is assigned to the text of the label, the Optional() part will not be included.
I’ve added support for the nil case if the question string is not defined.
You might also consider not making your dataObj array optional? what purpose does it serve to be optional? Seems to me that if you need to add items to the array then you know it should exist and since you've initialized it it will always exist but then may be empty. Instead just make it implicitly unwrapped and then return nil if there's no data, then the objects of the array won't all be optional.
if you have a default in mind that you would want the optional string to fall back to, a simple fix would be something like:
"\(q.question ?? "")"
which will default to an empty string if q.question is nil
also: be careful of all of your force unwraps. it might make more sense to have some guard statements or if let unwraps.
and swift array's can be written like so: var dataArray : [SHQuesAns]?
but there aren't many situations where you need to differentiate between a nil array and an empty array so you can just do var dataArray = [SHQuesAns]() and save yourself the need to unwrap
I am loading some data into NSUserDefaults on application startup and when the user views the according View loading the data out into a TableView. I'm having an issue with the data types though and always seem to get an error whichever way I try.
Where am I going wrong. I am skipping some code and only giving the important parts. I keep getting errors like "Can't assign Bool to AnyObject"
// startup function which loads the data
var categorys : [[String:Bool]] = [["All": true]]
for (index: String, category: JSON) in output {
categorys.append([category["name"].string!: true])
}
NSUserDefaults.standardUserDefaults().setObject(categorys, forKey: "categorys")
// view controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var categorys: [[String:Bool]] = []
func buildTableView() {
if let categorys = NSUserDefaults.standardUserDefaults().arrayForKey("categorys") {
for category in categorys {
self.categorys.append([category["name"] as! String: category["selected"] as! Bool])
}
// Was trying to just assign straight away but fails
// self.categorys = categorys
}
}
// then later on I want to be able to do the following
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
cell.textLabel?.text = self.categorys[indexPath.row]["name"] as? String
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.categorys[indexPath.row]["selected"] as? Bool == true) {
self.categorys[indexPath.row]["selected"] = "false"
}
}
}
I should probably also mention i was trying a different datatype before which is more logical for my usage but also had similar issues.
["name": [category["name"].string!, "selected": true]]
I should also mention this is data for my tableview and I want to update the boolean value for if the cell is selected to not.
You said in the comment that you've got an error about "finding nil", but it's not really the same as an error about "Can't assign Bool to AnyObject"...
For your nil error, you have to replace those forced typecasts:
category["name"] as! String
with optional binding:
if let name = category["name"] as? String {
}
Now it will fail but not crash if a property is nil, and will properly append to the array of dictionaries:
let category = ["name":"me", "selected":true]
var categorys: [[String:Bool]] = []
if let name = category["name"] as? String, let selected = category["selected"] as? Bool {
categorys.append([name:selected])
} else {
// oops, a value was nil, handle the error
}
Same for your other operations, don't use forced casts:
if let myBool = self.categorys[indexPath.row]["selected"] where myBool == true {
self.categorys[indexPath.row]["selected"] = false
}
And since self.categorys[indexPath.row]["selected"] is a Bool type, don't assign a string like "false" but the actual false value.
In order to use Bool in NSUserDefaults, you must use setBool: forKey: :
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "key")
otherwise it's problematic and you have to do workarounds, like storing the Bool in NSNumber or String.
I have a cellForItemAtIndexPath method I'm using for my UICollectionView. The reuse identifier depends on the indexPath, so I was planning to do something like:
var reuseIdentifier: String
if indexPath.row == 0 {
reuseIdentifier = "One"
} else if indexPath.row == 1 {
reuseIdentifier = "Two"
} else if indexPath.row == 2 {
reuseIdentifier = "Three"
}
var cell: UICollectionViewCell = collectionView.dequeueReusableCellWithIdentifier(reuseIdentifier, indexPath:indexPath)
Is this correct? Should reuseIdentifier be an optional String, or is it fine being a normal one?
Furthermore, am I handling it correctly in the nil case? If it's other than 2, reuseIdentifier is nothing, right? (Which is different from nil?) I should be handling this distinctly, right?
Why not something simpler, like this?
let reuseIdentifier = ["One", "Two", "Three"][indexPath.row]
Note: this will crash if indexPath.row > 2. If this might happen, then you should be more careful. You might consider something like this:
extension Array {
func at(index: Int) -> T? {
if index >= count {
return nil
} else {
return self[index]
}
}
}
// ...
let reuseIdentifier: String? = ["One", "Two", "Three"].at(indexPath.row) // might be nil
If you declare a var of non-optional type and never assign to it, it's a compile time error.
If you want to let it be nil, you'll need to declare it as an optional. Then you'll need to appropriately handle the case where it is nil -- since the table will accept a nil cell, you could do that.