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.
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)
}
}
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've been trying to implement a search function onto this iOS app for quite some time.
I have a JSON file holding all the data, which is then pulled and displayed onto the table view controller.
I've implemented the search bar and followed several tutorials, this one being with the fewest issues - http://www.ioscreator.com/tutorials/add-search-table-view-tutorial-ios8-swift (I've also used this too previously - https://www.youtube.com/watch?v=N9wcKc37ZXI)
My JSON file is structured as follows:
{ "animal":[
{
"no":"001",
"type":"dog",
"breed":"pitbull",
"classification":"mammal",
"sprite":"beast"
},
{
"no":"002",
"type":"dog",
"breed":"bulldog",
"classification":"mammal",
"sprite":"beast"
},
{
"no":"003",
"type":"cat",
"breed":"birman",
"classification":"mammal",
"sprite":"feline"
}
]}
I am calling the JSON data using a struct that is created in a separate file
Animal.swift
struct animalStruct {
static let path = NSBundle.mainBundle().pathForResource("animal", ofType: "JSON")
static let jsonData = NSData(contentsOfFile:path!, options: .DataReadingMappedIfSafe, error: nil)
static var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
}
And it is called into the table view controller using the following
AnimalTableViewController
var array : NSArray = Animal.animalStruct.jsonResult["animal"] as NSArray
Code used for the search:
AnimalTableViewController
#IBOutlet var segmentedSortOption: UISegmentedControl!
var array : NSArray = Animal.pokemonStruct.jsonResult["animal"] as NSArray
var filteredAnimal = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.tableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.searchDisplayController?.searchResultsTableView {
return self.filteredAnimal.count
} else {
return self.array.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var myCell:cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as cell
var upperCasedNames = array[indexPath.row]["name"] as? String
var searchedItem:String
if segmentedSortOption.selectedSegmentIndex == 0 {
if (self.resultSearchController.active) {
//**ISSUE OCCURS HERE**
myCell.name.text = filteredAnimal[indexPath.row]
return myCell
}
else {
myCell.name.text = upperCasedNames?.uppercaseString
return myCell
}
} else if segmentedSortOption.selectedSegmentIndex == 1 {
if let unsortedEvents = Animal.animalStruct.jsonResult["animal"] as? NSArray {
let descriptor = NSSortDescriptor(key: "name", ascending: true, selector: "caseInsensitiveCompare:")
let aToZ = unsortedEvents.sortedArrayUsingDescriptors([descriptor])
upperCasedNames = aToZ[indexPath.row]["name"] as? String
myCell.name.text = upperCasedNames?.uppercaseString
}
}
return myCell
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredAnimal.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let arrays = (array).filteredArrayUsingPredicate(searchPredicate!)
filteredArray = arrays as [[String]]
self.tableView.reloadData()
}
}
My question
The issue I have is that when I attempt to search via the UISearchBar, the moment I click on it, I get the "fatal error: array cannot be bridged from Objective-C". I believe this is happening because the arrays used in the tutorial is a different type compared to the ones I have used.
UPDATE
Firstly, the main issue was that the fact I had a different type of an array compared to the tutorial; so I had changed the var filteredAnimal = [String]() to var filteredArray = [[String:AnyObject]]() since the data source/JSON file had numerous arrays of data. And treat it as AnyObject due to that, which then allowed me to call the specific information from the JSON file.
The reason why I was returned with an empty array was because I did not assign the searched values in the empty array within the updateSearchResultsController method, so I had added the following just before reloading the table data - filteredArray = arrays as [[String:AnyObject]]
This still did not solve the issue as I had to change the if statement within the numberOfRowsInSection from tableView == self.searchDisplayController?.searchResultsTableView to self.resultSearchController.active, which then retrieved me an actual result.
However the search itself is still not working, but majority of the issues have been resolved. When tapping the search bar, it only retrieves the final index of the array, and when searching anything, it removes the value. I will continue to work on it and hopefully resolve the matter soon. Thanks for the help so far!
Below link may help you to solve your issue:
How can I fix "fatal error: can't index empty buffer"
in your code
let arrays = (array).filteredArrayUsingPredicate(searchPredicate!)
will create new reference of arrays.
Since the last update (which has played a major part for the solution) all I did was change the NSPredicate query from SELF CONTAINS[c] %# to name CONTAINS[c] %# and now when searching, it displays the correct matching object based on its name.
I'm quite new to working with Parse and I'm building a todo list as part of a CRM. Each task in the table view shows the description, due date, and client name. The description and due date are in my Task class, as well as a pointer to the Deal class. Client is a string in the Deal class. I'm able to query the description and due date properly, but I am not able to retrieve the client attribute from within the Deal object by using includeKey. I followed the Parse documentation for includeKey.
The description and due date show up properly in the resulting table view, but not the client. The log shows client label: nil and the printed task details include <Deal: 0x7ff033d1ed40, objectId: HffKOiJrTq>, but nothing about the client attribute. How can I retrieve and assign the pointer object's attribute (client) to my label within the table view? My relevant code is below. Thank you in advance.
Edit: I've updated my code with func fetchClients() based on this SO answer, but I'm still not sure whether my function is complete or where to call it.
class TasksVC: UITableViewController {
var taskObjects:NSMutableArray! = NSMutableArray()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println("\(PFUser.currentUser())")
self.fetchAllObjects()
self.fetchClients()
}
func fetchAllObjects() {
var query:PFQuery = PFQuery(className: "Task")
query.whereKey("username", equalTo: PFUser.currentUser()!)
query.orderByAscending("dueDate")
query.addAscendingOrder("desc")
query.includeKey("deal")
query.findObjectsInBackgroundWithBlock { (tasks: [AnyObject]!, error:NSError!) -> Void in
if (error == nil) {
var temp:NSArray = tasks! as NSArray
self.taskObjects = temp.mutableCopy() as NSMutableArray
println(tasks)
self.tableView.reloadData()
} else {
println(error?.userInfo)
}
}
}
func fetchClients() {
var task:PFObject = PFObject(className: "Task")
var deal:PFObject = task["deal"] as PFObject
deal.fetchIfNeededInBackgroundWithBlock {
(deal: PFObject!, error: NSError!) -> Void in
let client = deal["client"] as NSString
}
}
//MARK: - Tasks table view
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.taskObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as TaskCell
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "M/dd/yy"
var task:PFObject = self.taskObjects.objectAtIndex(indexPath.row) as PFObject
cell.desc_Lbl?.text = task["desc"] as? String
cell.date_Lbl.text = dateFormatter.stringFromDate(task["dueDate"] as NSDate)
cell.client_Lbl?.text = task["client"] as? String
var clientLabel = cell.client_Lbl?.text
println("client label: \(clientLabel)")
return cell
}
}
If the deal column is a pointer then includeKey("deal") will get that object and populate it's properties for you. There is no need to perform a fetch of any type on top of that.
You really should be using Optionals properly though:
if let deal = task["deal"] as? PFObject {
// deal column has data
if let client = deal["client"] as? String {
// client has data
cell.client_Lbl?.text = client
}
}
Alternatively you can replace the last if let with a line like this, which handles empty values and uses a default:
cell.client_Lbl?.text = (deal["client"] as? String) ?? ""
In your posted cellForRowAtIndexPath code you are trying to read client from the task instead of from the deal: task["client"] as? String.
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.