Implementing Search Functionality - ios

hello I have implemented an auto complete search on my app. I have cities stored in my mysql database and in app when user types any character or word, the app fetches result from the database and shows it. Now there is a small programming problem I am having and I don't know how to solve it.
The problem is in the same Array in which I am getting a City, I am getting country name and state name as well. As I have implemented a search only on cities not on state and country, I actually need the other columns(state,country) of those rows which are displaying based on user search city. I'll paste the code here for better understanding
class CityTableViewController: UITableViewController, UISearchResultsUpdating {
var dict = NSDictionary()
var filterTableData = [String]()
var resultSearchController = UISearchController()
var newTableData = [String]()
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 tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(self.resultSearchController.active){
return self.filterTableData.count
}else {
return dict.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CountryTableViewCell
if(self.resultSearchController.active){
cell.cityNameLabel.text = filterTableData[indexPath.row]
cell.countryNameLabel.text = get the country name
cell.stateNameLabel.text = get stateName
return cell
}else{
cell.cityNameLabel.text = (((self.dict["\(indexPath.row)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterTableData.removeAll(keepCapacity: false)
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
let searchPredict = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
print("searchPredict is \(searchController.searchBar.text!)")
for var i = 0; i < self.dict.count; i++ {
let cityName = (((self.dict["\(i)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
let countryName = (((self.dict["\(i)"] as?NSDictionary)!["Country"] as?NSDictionary)!["name"] as?NSString)! as String
let stateName = (((self.dict["\(i)"] as?NSDictionary)!["State"] as?NSDictionary)!["name"] as?NSString)! as String
newTableData.append(cityname)
}
let array = (newTableData as NSArray).filteredArrayUsingPredicate(searchPredict)
print("array is\(array)")
filterTableData = array as! [String]
self.tableView.reloadData()
}
func getCityNamesFromServer(searchWord:String){
let url:String = "http://localhost/"
let params = ["city":searchWord]
ServerRequest.postToServer(url, params: params) { result, error in
if let result = result {
print(result)
self.dict = result
}
}
}
}
If I try to setup new array of state and country then data doesn't shows up correctly. cities don't belong to his own state shows up. So How I can keep the order correctly.
Array:
dict
0 = {
City = {
code = 10430;
"country_id" = 244;
id = 8932;
name = Laudium;
"state_id" = 4381;
"updated_at" = "<null>";
};
Country = {
id = 244;
name = "South Africa";
};
State = {
"country_id" = 244;
id = 4381;
name = Gauteng;
};
}; etc
newTableData
["Lynnwood", "Lyndhurst", "Laudium"] etc
filterTableData
["Laudium", "La Lucia", "Lansdowne"] etc

You should search the dictionary for the matches and store the matched keys in an array and reference to these keys in the results.
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
self.filteredKeys.removeAll()
for (key, value) in self.dict {
let valueContainsSearchWord: Bool = (((value as? NSDictionary)?["City"] as? NSDictionary)?["name"] as? String)?.uppercaseString.containsString(searchWord.uppercaseString) ?? false
if valueContainsSearchWord {
self.filteredKeys.append(key as! String)
}
}
self.tableView.reloadData()
}
Fill the tableview with this filtered keys:
let key = self.filteredKeys[indexPath.row]
let dictionary = self.dict[key] as! NSDictionary
cell.cityNameLabel.text = ((dictionary["City"] as? NSDictionary)!["name"] as? NSString)! as String
cell.countryNameLabel.text = ((dictionary["Country"] as? NSDictionary)!["name"] as? NSString)! as String
cell.stateNameLabel.text = ((dictionary["State"] as? NSDictionary)!["name"] as? NSString)! as String
return cell
Just save this filtered dictionary (self.filteredDictionary) and use that to populate the tableView.
I think the other problem is, when you call the server's search method (getCityNamesFromServer:) from updateSearchResultsForSearchController: the response from the server comes asynchronously and the process afterwards is using the old dictionary data, because the new one is not ready at the time of the processing.
You should try modifying the getCityNamesFromServer: method with a block completion like this:
func updateSearchResultsForSearchController(searchController: UISearchController) {
// Get search word
getCityNamesFromServer(searchWord) { () -> Void in
// Rest of the code comes here
}
}
func getCityNamesFromServer(searchWord:String, completionHandler: (() -> Void) ) {
let url:String = "http://localhost/"
let params = ["city":searchWord]
ServerRequest.postToServer(url, params: params) { result, error in
if let result = result {
print(result)
self.dict = result
}
completionHandler()
}
}

Related

Attempting to get numberOfRowsInSection count to be equal to an array count from another file in Swift

Currently I am fetching data from firebase, turning the data into an array of objects and trying to have that array count be the count of the "numberOfRowsInSection".
So, I get the right count on the view controller where the array is declare but I keep getting 0's when I pass that data to the tableview controller.
Below I am showing the viewcontroller where I am fetching data and putting it in a filtered array:
func fetchAllData( completion: #escaping (_ call: [Restaurant]) -> Void) {
self.ref = Database.database().reference()
self.ref?.observe(.value, with: { (snap) in
guard let topArray = snap.value as? [[String:Any]] else {print(":(") ; return }
var restaurantArray = [Restaurant]()
for dictionary in topArray {
self.restaurantGroup.enter()
guard let address = dictionary["Address"] as? String,
let city = dictionary["City"] as? String,
let inspectionDate = dictionary["Inspection Date"] as? String,
let name = dictionary["Name"] as? String,
let major = dictionary["Number of Occurrences (Critical Violations)"] as? Int,
let minor = dictionary["Number of Occurrences (Noncritical Violations)"] as? Int,
let violationTitle = dictionary["Violation Title"] as? String else { continue }
print(2)
//MARK: - creates restaurants from the list above
let restaurant = Restaurant(address: address, city: city, inspectionDate: inspectionDate, name: name, major: major, minor: minor, violationTitle: violationTitle)
print(3)
//MARK: - Adds a restaurant to restaurant array instance
restaurantArray.append(restaurant)
}
self.restaurantList = restaurantArray
self.restaurantGroup.leave()
completion(self.restaurantList)
let dictionaryNew = Dictionary(grouping: self.restaurantList) { $0.name + " " + $0.address}
self.restaurantListModified = dictionaryNew
print(self.restaurantListModified.count)
})
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
var filteredArray = [[Restaurant]]()
guard let userSearch = searchBar.text?.uppercased() else { return }
pulleyViewController?.setDrawerPosition(position: .partiallyRevealed, animated: true)
var nameArray = [String]()
for (key, value) in restaurantListModified {
if value[0].name.hasPrefix(userSearch.uppercased()){
filteredArray.append(value)
}
}
for subarray in filteredArray {
let nameArrayForTBView = subarray[0].name
nameArray.append(nameArrayForTBView)
}
self.tableViewNameArray = nameArray
print("\(tableViewNameArray.count)πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹πŸ˜‹")
print("this First")
}
}
The tableViewNameArray is the array I am trying to pass to this tableView Controller
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("This second")
print(MapViewController.sharedMapViewController.tableViewNameArray.count)
return MapViewController.sharedMapViewController.tableViewNameArray.count
}
I also have my array set to a Notification Center that is received in the tableView Controller as shown below:
var tableViewNameArray = [String]() {
didSet {
DispatchQueue.main.async {
DispatchQueue.main.async {
NotificationCenter.default.post(name: MapViewController.RestaurantNotification.notificationSet, object: self)
}
}
}
}
Points:
My array from viewcontroller is getting the right count on the view controller but the accurate count is not being passed to the tableView Controller.
the tableview count for the array being passed is 0
I found the problem that #Kamran pointed to. I was not assigning self.tableViewNameArray = nameArray. So I changed
var tableViewNameArray to
static var tableViewNameArray
If you're new to programming like I, make sure you read a bit more on local and global variables. Very helpful.

Array filled and displayed in function but no displayed in tableview swift

I am having trouble with an array that it is filled correctly in a separated function, the issue is when i try to fill in the elements of my cell in my tableview, i can only find the last element however when i want to display the number of elements in that array while filling the cell it displays the correct number of elements, can anybody help please.
this is my function for retrieving and filling in the array:
func downloadUserDetails(completed: #escaping DownloadComplete){
let Ful_Url = "http://192.168.1.4:8888/phps/select.php"
Alamofire.request(Ful_Url).responseJSON(completionHandler: { (response) in
if let userDect = response.result.value as? [Dictionary<String,AnyObject>]{
for ex in 0...userDect.count-1
{
if let firstnames = userDect[ex]["firstname"] as? String{
self.users?.firstname = firstnames}
if let emails = userDect[ex]["email"] as? String{
self.users?.email = emails}
if let lastnames = userDect[ex]["lastname"] as? String{
self.users?.lastname = lastnames}
print("---------------------------------")
self.items.append(self.users!)
// self.items.insert(self.users!, at: self.i)
print(self.items[ex].email)
print(self.items.count)
}
}
completed()
self.tableview.reloadData()
})
}
this is how i am trying to fill the cell's labels:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("####################")
print("nombre items")
print(self.items.count)
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier:"myCell" , for:indexPath)
let email:UILabel = cell.viewWithTag(11) as! UILabel
let firstname:UILabel = cell.viewWithTag(12) as! UILabel
let lastname:UILabel = cell.viewWithTag(13) as! UILabel
print("=========================email=========================")
print(items[indexPath.row].email)
email.text = items[indexPath.row].email
firstname.text = items[indexPath.row].firstname
lastname.text = items[indexPath.row].lastname
return cell
}
I think trouble in insert method:
self.item.insert(self.users!, at:self,i);
You can try :
self.item.insert(self.users!, at:ex);
I think that your issue is that you are using one single instance of user and then appending it to the array, Each item in the array points to the same item (classes are passed by reference).
You do not need to do this, you dont need to maintain a count or index during iteration either.
This code should work fine..
if let usersDict = response.result.value as? [Dictionary<String,AnyObject>] {
self.users = usersDict.map({ dict in
let user = User()
if let firstnames = dict["firstname"] as? String{
user.firstname = firstnames }
if let emails = dict["email"] as? String{
user.email = emails }
if let lastnames = dict["lastname"] as? String{
user.lastname = lastnames }
return user
})
self.tableView.reloadData()
}
Or even better, allow your User object to be intialised with a dictionary and then do
if let usersDict = response.result.value as? [Dictionary<String,AnyObject>] {
self.users = usersDict.map({ User($0) })
self.tableView.reloadData()
}
Just use local variables during your loop, no need for class properties here. To use the bottom one, you will need to be able to initialise the User object with a dictionary. Similar to this method:
struct User
{
var firstName:String
var lastName:String
var email:String
init(dict:Dictionary<String,AnyObject>) {
email = dict["email"] as? String
firstName = dict["firstName"] as! String
lastName = dict["lastName"] as! String
}
}
UPDATE:
I just wrote this in a playground which works fine
class User {
var firstName: String!
var lastName: String!
var email: String!
init(dict: [String:AnyObject]) {
self.firstName = dict["firstName"] as! String
self.lastName = dict["lastName"] as! String
self.email = dict["email"] as! String
}
}
let usersDict: [[String:String]] = [
["firstName": "John", "lastName": "Smith", "email": "john#example.com"],
["firstName": "John", "lastName": "Smithy", "email": "john#example.com"],
["firstName": "John", "lastName": "Stevens", "email": "john#example.com"],
["firstName": "John", "lastName": "Smithen", "email": "john#example.com"]
]
let users = usersDict.map({ User(dict: $0 as! [String : AnyObject]) })
for user in users {
print(user.firstName, user.lastName)
}
Output:
John Smith
John Smithy
John Stevens
John Smithen
Actually i have just found the solution for any one who faces the same problem, it is actually very simple, the declaration of Class User should be inside the loop, not as a class variable, so now i create a new user at each element found and i add the old element to the array.

how to use getter setter method for search bar in swift 3.1.1?

I have implemented search bar in my app. I have a JSON data of some hotels. (name, image, address, mobile number, email). I am getting the name in the search result, but I want to get the entire data of a particular hotel. So, please help me to implement search functionality. I want to use getter setter method. Please help. Thanks in advance!!
let str = self.catArr [indexPath.row]
let url1 = "myapi"
let url2 = url1+str
let allowedCharacterSet = (CharacterSet(charactersIn: " ").inverted)
if let url = url2.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)
{
Alamofire.request(url).responseJSON { (responseData) -> Void in
if (responseData.result.value) == nil
{
print("Error!!")
}
//if (responseData.result.value) != nil
else
{
let response = JSON(responseData.result.value!)
if let resData = response["data"].arrayObject
{
self.arrResponse1 = resData as! [[String: AnyObject]]
for item in self.arrResponse1
{
let name = item["name"] as! String
self.arrName.append(name)
let add = item["address"] as! String
self.arrAddress.append(add)
let web = item["website"] as! String
self.arrWebsite.append(web)
let email = item["email"] as! String
self.arrEmail.append(email)
let mob = item["mobile"] as! String
self.arrMobile.append(mob)
let city = item["city"] as! String
self.arrCity.append(city)
let state = item["state"] as! String
self.arrState.append(state)
let dist = item["district"] as! String
self.arrDistrict.append(dist)
let area = item["area"] as! String
self.arrArea.append(area)
let img = item["image"] as! String
self.arrImage.append(img)
let rating = item["rating"] as! String
self.arrRating.append(rating)
let id = item["id"] as! String
self.arrId.append(id)
}
}
}
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
{
filteredName = self.nameArr.filter({ (String) -> Bool in
let tmp: NSString = String as NSString
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filteredName.count == 0)
{
searchActive = false;
}
else
{
searchActive = true;
print("Search Array = \(filteredName)")
}
self.infoTableView.reloadData()
}
//showing in tableview
if(searchActive)
{
cell.lblName.text = self.filteredName [indexPath.row]
}
else
{
let urlImage = str1+self.imageArr [indexPath.row]+".jpg"
cell.imgView.sd_setImage(with: URL(string: urlImage), placeholderImage: UIImage(named: ""))
cell.lblName.text = self.nameArr [indexPath.row]
cell.lblAddress.text = self.addressArr [indexPath.row]
cell.lblCity.text = self.cityArr [indexPath.row]
cell.lblPhone.text = self.mobileArr [indexPath.row]
cell.lblEmail.text = self.emailArr [indexPath.row]
cell.lblStar.text = self.ratingArr [indexPath.row]
cell.phoneString = self.mobileArr [indexPath.row]
cell.emailString = self.emailArr [indexPath.row]
}
Instead of having multiple array to store each value what you need is array of custom class/struct object and then its easy to use filter with it.
struct Item { //Make some suitable name
let id: String
let name: String
let address: String
let website: String
let email: String
let mobile: String
let city: String
let state: String
let district: String
let area: String
let image: String
let rating: String
init(dictionary: [String:Any]) {
self.id = dictionary["id"] as? String ?? "Default Id"
self.name = dictionary["name"] as? String ?? "Default name"
self.address = dictionary["address"] as? String ?? "Default address"
self.website = dictionary["website"] as? String ?? "Default website"
self.email = dictionary["email"] as? String ?? "Default email"
self.mobile = dictionary["mobile"] as? String ?? "Default mobile"
self.city = dictionary["city"] as? String ?? "Default city"
self.state = dictionary["state"] as? String ?? "Default state"
self.district = dictionary["district"] as? String ?? "Default district"
self.area = dictionary["area"] as? String ?? "Default area"
self.image = dictionary["image"] as? String ?? "Default image"
self.rating = dictionary["rating"] as? String ?? "Default rating"
}
}
Now you need only two array of type [Item] one for normal data and one for filter data, so declare two array like this.
var items = [Item]()
var filterItems = [Item]()
Initialized this items array like this way in your Alamofire request.
let response = JSON(responseData.result.value!)
if let resData = response["data"].arrayObject as? [[String: Any]] {
self.items = resData.map(Item.init)
}
Now use this two array in tableView method like this.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchActive {
return filterItems.count
}
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! YourCustomCell
let item = searchActive ? filterItems[indexPath.row] : items[indexPath.row]
cell.lblName.text = item.name
cell.lblAddress.text = item.address
//...Set others detail same way
return cell
}
Now with textDidChange filter your items array this way.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
{
filterItems = self.items.filter({
$0.name.localizedCaseInsensitiveContains(searchText)
})
if(filterItems.count == 0) {
searchActive = false
}
else {
searchActive = true
print("Search Array = \(filterItems)")
}
self.infoTableView.reloadData()
}

Autocomplete Search gets slow and takes memory while it fetches the result from database

hello I have implemented an auto complete search on my app. I have cities stored in my mysql database and in app when user types any character or word, the app fetches result from the database and shows it. The problem which I am having is there are more then 1000 cities stored in database and when user lets say type one character my app keyboard got stuck a little and it takes a lot of memory while it fetches the result and shows it. Is there any better way to implement this kind a functionality. Please Please look at my code and let me know what changes should I done in my code
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterTableData.removeAll(keepCapacity: false)
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
let searchPredict = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
for var i = 0; i < self.dict.count; i++ {
let cityname = (((self.dict["\(i)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
newTableData.append(cityname)
}
let array = (newTableData as NSArray).filteredArrayUsingPredicate(searchPredict)
print("array is\(array)")
filterTableData = array as! [String]
self.tableView.reloadData()
}
Full Code:
class CityTableViewController: UITableViewController, UISearchResultsUpdating {
var dict = NSDictionary()
var filterTableData = [String]()
var resultSearchController = UISearchController()
var newTableData = [String]()
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 tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(self.resultSearchController.active){
return self.filterTableData.count
}else {
return dict.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CountryTableViewCell
if(self.resultSearchController.active){
cell.cityNameLabel.text = filterTableData[indexPath.row]
return cell
}else{
cell.cityNameLabel.text = (((self.dict["\(indexPath.row)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterTableData.removeAll(keepCapacity: false)
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
let searchPredict = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
print("searchPredict is \(searchController.searchBar.text!)")
for var i = 0; i < self.dict.count; i++ {
let cityname = (((self.dict["\(i)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
newTableData.append(cityname)
}
let array = (newTableData as NSArray).filteredArrayUsingPredicate(searchPredict)
print("array is\(array)")
filterTableData = array as! [String]
self.tableView.reloadData()
}
func getCityNamesFromServer(searchWord:String){
let url:String = "http://localhost/"
let params = ["city":searchWord]
ServerRequest.postToServer(url, params: params) { result, error in
if let result = result {
print(result)
self.dict = result
}
}
}
}
The best way to limit the amount of data is returned by adding a LIMIT clause to your query. Example SQL query:
SELECT name FROM cities WHERE name like '%something%';
Change this to:
SELECT name FROM cities WHERE name like '%something%' LIMIT 10;
From the user point of view it does not make sense to return 1000 rows, you would not be able to display it anyways, therefore you have to come up with a number that fits on your screen. After any additional key press you can repeat the query with the updated search string.

How to unwrap my variables used in UILabels properly?

EDIT 1: Added my revision cellForRowAtIndexPath code at the bottom of the post
EDIT 2: Added my new EditorialElement code
I am having difficulty properly unwrapping my UILabel text inputs properly, so all of my text says "Optional(Author name)" (for example, this is an app for a newspaper). I have tried to force unwrap my variables in different ways but was not able to make it work.
The text input for my UILabels are created in the following way. The corresponding class is "EditorialElement", which has the following property definitions:
class EditorialElement: NSObject {
var title: String! // title
var nodeID: Int? // nid
var timeStamp: Int // revision_timestamp
var imageURL: String? // image_url
var author: String? // author
var issueNumber: String! // issue_int
var volumeNumber: String! //Β volume_int
var articleContent: String! // html_content
/* To get an NSDate objec from Unix timestamp
var date = NSDate(timeIntervalSince1970: timeStamp) */
init(title: String, nodeID: Int, timeStamp: Int, imageURL: String, author: String, issueNumber: String, volumeNumber: String, articleContent: String) {
self.title = title
self.nodeID = nodeID
self.timeStamp = timeStamp
self.imageURL = imageURL
self.author = author
self.issueNumber = issueNumber
self.volumeNumber = volumeNumber
self.articleContent = articleContent
}
override func isEqual(object: AnyObject!) -> Bool {
return (object as! EditorialElement).nodeID == self.nodeID
}
override var hash: Int {
return (self as EditorialElement).nodeID!
}
}
Then, I use this class to retrieve data from my JSON file and parse it into an "editorialObjects" array (sorry about all the commenting and bad spacing):
func populateEditorials() {
if populatingEditorials {
return
}
populatingEditorials = true
Alamofire.request(GWNetworking.Router.Editorials(self.currentPage)).responseJSON() { response in
if let JSON = response.result.value {
/*
if response.result.error == nil {
*/
/* Creating objects for every single editorial is long running work, so we put that work on a background queue, to keep the app very responsive. */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
/* Making an array of all the node IDs from the JSON file */
var nodeIDArray : [Int]
if (JSON .isKindOfClass(NSDictionary)) {
for node in JSON as! Dictionary<String, AnyObject> {
let nodeIDValue = node.0
var lastItem : Int = 0
self.nodeIDArray.addObject(nodeIDValue)
if let editorialElement : EditorialElement = EditorialElement(title: "init", nodeID: 0, timeStamp: 0, imageURL: "init", author: "init", issueNumber: "init", volumeNumber: "init", articleContent: "init") {
editorialElement.title = node.1["title"] as! String
editorialElement.nodeID = Int(nodeIDValue)
let timeStampString = node.1["revision_timestamp"] as! String
editorialElement.timeStamp = Int(timeStampString)!
editorialElement.imageURL = String(node.1["image_url"])
editorialElement.author = String(node.1["author"])
editorialElement.issueNumber = String(node.1["issue_int"])
editorialElement.volumeNumber = String(node.1["volume_int"])
editorialElement.articleContent = String(node.1["html_content"])
lastItem = self.editorialObjects.count
print (editorialElement.nodeID)
self.editorialObjects.addObject(editorialElement)
/* Sorting the elements in order of newest to oldest (as the array index increases] */
let timestampSortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
self.editorialObjects.sortUsingDescriptors([timestampSortDescriptor])
let indexPaths = (lastItem..<self.editorialObjects.count).map { NSIndexPath(forItem: $0, inSection: 0) }
/*
nodeIDArray[nodeCounter] = jsonValue{nodeCounter}.string
let editorialInfos : EditorialElement = ((jsonValue as! NSDictionary].1["\(nodeIDArray[nodeCounter]]"] as! [NSDictionary]].map { EditorialElement(title: $0["title"] as! String, nodeID: $0["nid"] as! Int, timeStamp: $0["revision_timestamp"] as! Int, imageURL: $0["image_url"] as! String, author: $0["author"], issueNumber: $0["issue_int"] as! Int, volumeNumber: $0["volume_int"] as! Int, articleContent: $0["html_content"] as! String] // I am going to try to break this line down to simplify it and fix the build errors
*/
}
print(self.editorialObjects.count)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.editorialsTableView.reloadData()
}
self.currentPage++
}
}
self.populatingEditorials = false
}
}
And then I just use those objects for my labels in my cellForRowAtIndexPath method:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as! EditorialsTableViewCell
let title = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).title
let timeStamp = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).timeStamp
let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(Int((editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).timeStamp)))
timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)
let imageURL = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).imageURL
let author : String! = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).author!
let issueNumber = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).issueNumber
let volumeNumber = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).volumeNumber
let articleContent = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).articleContent
let nodeID = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).nodeID
/* Unlike the Pictures Collection View, there is no need to create another Alamofire request here, since we already have all the content we want from the JSON we downloaded. There is no URL that we wish to place a request to to get extra content. */
cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.editorialHeadlineLabel.text = title
cell.editorialAuthorLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialAuthorLabel.text = author
cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialPublishDateLabel.text = timeStampDateString
return cell
}
Where should I be force-unwrapping my variables ?
EDIT 1: Revised code
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
guard let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as? EditorialsTableViewCell else {
print ("error: editorialsTableView cell is not of class EditorialsTableViewCell, we will use RandomTableViewCell instead")
return tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as! RandomTableViewCell
}
if let editorialObject = editorialObjects.objectAtIndex(indexPath.row) as? EditorialElement {
// we just unwrapped editorialObject
let title = editorialObject.title ?? "" // if editorialObject.title == nil, then we return an empty string.
let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(editorialObject.timeStamp))
let timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)
let author = editorialObject.author ?? ""
let issueNumber = editorialObject.issueNumber ?? ""
let volumeNumber = editorialObject.volumeNumber ?? ""
let articleContent = editorialObject.articleContent ?? ""
let nodeID = editorialObject.nodeID ?? 0
cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.editorialHeadlineLabel.text = title
cell.editorialAuthorLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialAuthorLabel.text = String(author)
cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialPublishDateLabel.text = timeStampDateString
} else {
}
return cell
}
EDIT 2: new EditorialElement code
class EditorialElement: NSObject {
var title: String // title
var nodeID: Int // nid
var timeStamp: Int // revision_timestamp
var imageURL: String // image_url
var author: String // author
var issueNumber: String // issue_int
var volumeNumber: String //Β volume_int
var articleContent: String // html_content
/* To get an NSDate objec from Unix timestamp
var date = NSDate(timeIntervalSince1970: timeStamp) */
init(title: String, nodeID: Int, timeStamp: Int, imageURL: String, author: String, issueNumber: String, volumeNumber: String, articleContent: String) {
self.title = title
self.nodeID = nodeID
self.timeStamp = timeStamp
self.imageURL = imageURL
self.author = author
self.issueNumber = issueNumber
self.volumeNumber = volumeNumber
self.articleContent = articleContent
}
override func isEqual(object: AnyObject!) -> Bool {
return (object as! EditorialElement).nodeID == self.nodeID
}
override var hash: Int {
return (self as EditorialElement).nodeID
}
}
Couple things. 1. You should only be force unwrapping them if you know that there is going to be something there. But if you're super confident in that (or you don't expect/want the app to work without them) then you should just have them be forced unwrapped from the get go. Theres no point in doing var imageURL: String? only to write imageURL! everywhere in the code. The point of optionals is to allow you to gracefully handle situations wherein the object might be nil.
Anyway, assuming this is the correct structure for you the first thing I would do is if let the return of object at index. So writing:
if let element = editorialObjects.objectAtIndex(indexPath.row) as? EditorialElement
Now you can use element as an EditorialElement throughout cell for row. From there you can decide how/when to unwrap everything else.
So let author : String! = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).author! would become either
let author = element.author! or you could if let to avoid a crash and handle the case where it is nil. if let author = element.author { // do something }
My opinion, change your variable declaration to not nil variable, example: var author: String!
Then, when you set value to your variable, set it is empty character (or default value) if it's nil:
editorialElement.author = String(node.1["author"]) ?? ""
After that, you can use your variable without unwrap everywhere.
In most of the cases forced unwrapping is a code smell. Don't use it unless you're linking IBOutlets or in some other exceptional cases. Try to properly unwrap your variables.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
guard let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as? EditorialsTableViewCell else {
// if we fall here cell isn't a EditorialsTableViewCell
// handle that properly
}
if let editorialObject = editorialObjects.objectAtIndex(indexPath.row) as? EditorialElement {
// editorialObject here is unwrapped
let title = editorialObject.title ?? "" // if editorialObject.title == nil we store empty string
let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(editorialObject.timeStamp))
let timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)
cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.editorialHeadlineLabel.text = title
cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
cell.editorialPublishDateLabel.text = timeStampDateString
} else {
// no such editorial object - log error.
// return empty cell or do more sofisticated error handling
}
return cell
}

Resources