Expandable cells in Swift table - cell reuse/dequeue? - swift

I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.
I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.
When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.
I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?
FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:
https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
This one looks interesting but is not in Swift:
https://github.com/singhson/Expandable-Collapsable-TableView
Same comment:
https://github.com/OliverLetterer/SLExpandableTableView
This looks very interesting - well supported - but I haven't had time to investigate:
https://github.com/Augustyniak/RATreeView
A similar request here:
Expand cell when tapped in Swift
A similar problem described here, but I think I'm already doing what is suggested?
http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
Here is my table view controller code. I believe the problem is in the...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
... function, but for the life of me I can't see it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
test = defineCellProps() // This loads my hard-coded cell properties into array "test"
configureTableView()
}
func configureTableView() {
loadCellDescriptors()
tblExpandable.delegate = self
tblExpandable.dataSource = self
tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield") // There are additional cell types that are not shown and not related to the problem
}
func loadCellDescriptors() { // Puts the data from the "test" array into the format used in the original example
for section in 0..<ACsections.count {
var sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
cellDescriptors.append(sectionProps)
}
cellDescriptors.remove(at: 0) // Removes the empty row
getIndicesOfVisibleRows()
tblExpandable.reloadData() // The table
}
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors { // cellDescriptors is an array of sections, each containing an array of cell dictionaries
var visibleRows = [Int]()
let rowCount = (currentSectionCells as AnyObject).count as! Int
for row in 0..<rowCount { // Each row is a table section, and array of cell dictionaries
var testDict = currentSectionCells[row]
if testDict["isVisible"] as! Bool == true {
visibleRows.append(row)
} // Close the IF
} // Close row loop
visibleRowsPerSection.append(visibleRows)
} // Close section loop
} // end the func
func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
return cellDescriptor as [String : AnyObject]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.textLabel?.text = nil
cell.detailTextLabel?.text = nil
cell.textField?.placeholder = nil
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
if primaryTitle as! String == "" {
cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
cell.textLabel?.text = nil
} else {
cell.textField.placeholder = nil
cell.textLabel?.text = primaryTitle as? String
}
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = "some text"
}
cell.detailTextLabel?.text = "some text"
// This next line, when enabled, always puts the correct row number into each cell.
// cell.textLabel?.text = "cell number \(indexPath.row)."
}
cell.delegate = self
return cell
}
Here is the CustomCell code with almost no changes by me:
import UIKit
protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray
var delegate: CustomCellDelegate!
override func awakeFromNib() {
super.awakeFromNib() // Initialization code
if textLabel != nil {
textLabel?.font = bigFont
textLabel?.textColor = primaryColor
}
if detailTextLabel != nil {
detailTextLabel?.font = smallFont
detailTextLabel?.textColor = secondaryColor
}
if textField != nil {
textField.font = bigFont
textField.delegate = self
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() { // I added this and it did not help
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if delegate != nil {
delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
}
return true
}
}

OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
Can you see what's missing?
textField?.text = nil
That's all it took! I was mucking about with the label but not the textfield text itself.

Related

fetch data from firebase and use textfield for preview and update purposes

I want to use the same objects of one ViewController for saving into Firebase and for fetching saved data to preview and update if necessary.
Initially I used textfield in static cells it worked pretty well, but fail to insert text in textfield in dynamic cell.
When I call print function for the textfield in console it prints out correct value, but doesn't show anything on screen of simulator. I even tried to use simple strait text string to put it into textfield, but unsuccessful.
here is related code from TextMessageViewController, which i use for sending data to Firebase through textfields in dynamical tablecells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TextInputTableViewCell = receiverEmailTableView.dequeueReusableCell(withIdentifier: "ReceiverEmail") as! TextInputTableViewCell!
cell.recepientEmailTF.delegate = self
cell.recepientEmailTF.tag = indexPath.row
return cell
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
if MyGlobalVariables.emails.count <= 3 {
print("tag master = \(textField.tag)")
switch textField.tag {
case 0:
if MyGlobalVariables.emails.endIndex == 0 {
MyGlobalVariables.emails.append(textField.text!)
}
MyGlobalVariables.emails[0] = textField.text!
case 1:
if MyGlobalVariables.emails.endIndex == 1 {
MyGlobalVariables.emails.append(textField.text!)
}
MyGlobalVariables.emails[1] = textField.text!
case 2:
if MyGlobalVariables.emails.endIndex == 2 {
MyGlobalVariables.emails.append(textField.text!)
}
MyGlobalVariables.emails[2] = textField.text!
default:
print("exceeded")
}
DispatchQueue.main.async {
self.receiverEmailTableView.reloadData()
}
} else {
print("exceeded emails limit, add alert")
}
}
Portion of code from TextPreviewViewController from where I want to get firebase data and add it to texfields. This viewcontroller is connected to preview viewcontroller in storyboard
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let edit = UITableViewRowAction(style: .default, title: "Edit") { (action, indexPath) in
let newMessageVC = self.storyboard?.instantiateViewController(withIdentifier: "TextMessage") as? TextMessageViewController
newMessageVC?.modalPresentationStyle = .overCurrentContext
self.present(newMessageVC!, animated: true, completion: {
let updateButton = newMessageVC?.saveOrUpdateButton
updateButton?.titleLabel?.text = "Update"
let messageBody = newMessageVC?.messageTV
let dateField = newMessageVC?.tergetDateTF
let action = MyGlobalVariables.refMessages.child(MyGlobalVariables.uidUser!)
// CONCERN POINT: from here->
let cell1: TextInputTableViewCell = newMessageVC?.receiverEmailTableView.dequeueReusableCell(withIdentifier: "ReceiverEmail") as! TextInputTableViewCell!
cell1.recepientEmailTF.delegate = self
cell1.recepientEmailTF.allowsEditingTextAttributes = true
let texfielf = cell1.recepientEmailTF
MyGlobalVariables.emails.removeAll()
MyGlobalVariables.emails = ["","",""]
// cell1.recepientEmailTF.text = "Suka blyat" <- even this simple text doesnt appear
MyGlobalVariables.emails[0].append(self.messages[indexPath.row].email1!)
texfielf?.text = MyGlobalVariables.emails[0]
//cell1.recepientEmailTF.text = MyGlobalVariables.emails[0] <- this code also doesnt work
MyGlobalVariables.emails[1].append(self.messages[indexPath.row].email2!)
texfielf?.text = self.messages[indexPath.row].email2!
MyGlobalVariables.emails[2].append(self.messages[indexPath.row].email3!)
texfielf?.text = self.messages[indexPath.row].email3!
DispatchQueue.main.async {
newMessageVC?.receiverEmailTableView.reloadData()
}
//CONCERN POINT: ->up to here
messageBody?.text = self.messages[indexPath.row].message!
dateField?.text = self.messages[indexPath.row].setupDate!
if let autoID2 = self.messages[indexPath.row].autoID {
MyGlobalVariables.messageForUpdate1.append(autoID2) }
})
}
return [edit]
}
My UITableViewCell class
public class TextInputTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var recepientEmailTF: UITextField!
override public func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}}
I would appreciate any help or advices.

UITableView cellForRowAtIndexPath function not updating after reload function

I am trying to reload data in my tableview after getting the data from a delegate method. But the issue is that all the data is not coming through to the tableView function cellForRowAtIndexPath. I have set the variable self.restNames to hold the values from the delegate method which comes around 4 values but not all of them show up in the function cellForRowAtIndexPath. When I change the tabs and go back to the tableView, some data does come through but not all of it.Will appreciate any help on this. Apologies if I have missed something, I am new to Swift and haven't raised much questions in StackOverflow.
Regards,
Saurabh
Below is my the code for the tableViewController
class RestTableViewController: UITableViewController,getDistanceTime {
var RestTable = [Restaurant]()
let label = UILabel()
let menuUrl = "menu url"
let listProjectUrl = "url"
override func viewDidLoad() {
super.viewDidLoad()
let headerView = self.tableView
headerView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
headerView.tableHeaderView?.backgroundColor = UIColor.redColor()
self.tableView.tableHeaderView = self.tableView.tableHeaderView
self.tableView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
let token = keychain[""]
let menuData = Alamofire.request(Method.GET, self.menuUrl, headers: ["Authorization":"JWT \(token!)"])
menuData.responseJSON{ response in
let data = JSON(response.result.value!)
var localmenu = [menu]()
for (_,item) in data{
let menuOne = menu(place: item["place"].string!, types: item["types"].string!, name: item["name"].string!, price: item["price"].string!)
localmenu.append(menuOne)
}
self.menus = localmenu
}
}
// Delegate method
func loadWithDisTime(distance: String,name: String)
{
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.userDistanceFromLocation = distance
//This will hold 4 values
self.restNames = name
self.tableView.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.getDistance = getDistanceTimeVC()
self.getDistance.delegate = self
let restCount = self.RestTable.count
if restCount == 0
{
let alertView = UIAlertController(title: "There are no restaurants near your location", message: "Press Okay to go back", preferredStyle: UIAlertControllerStyle.Alert)
let alertAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.Cancel, handler: nil)
alertView.addAction(alertAction)
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
for item in 0...restCount - 1
{
self.restLat = self.RestTable[item].lat
self.restLng = self.RestTable[item].lng
self.getDistance.getDistance(self.userLat, userLng: self.userLng, restLat: self.restLat, restLng: self.restLng,restName: self.RestTable[item].name)
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.RestTable.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! RestTableViewCell
//Not all values coming through here
print(self.restNames)
if self.RestTable[indexPath.row].name == self.restNames
{
cell.textLabel?.text = "\(indexPath.row + 1) - \(self.RestTable[indexPath.row].name) - \(self.userDistanceFromLocation)"
cell.backgroundColor = UIColor.whiteColor()
cell.textLabel?.textColor = UIColor.blackColor()
cell.textLabel?.font = UIFont(name: "Avenir-Heavy", size: 15)
}
return cell
}
In this block leave only reload table view
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.tableView.reloadData()
}

Conditionally activate UISearchController for iOS8+

I have an app that I would like to target to iOS 7, but am currently unable to do so because it contains a UISearchController which is only available for iOS 8 and up.
I've been trying to modify the search bar with a UISearchDisplayController so that it works in iOS 7, but I just can't get it to work and am getting a little frustrated. So as a temporary measure (while I get better at coding), I would like to disable the search bar for any iOS below 8.0 - which won't impact the end user too much because its only one screen in a much larger app.
The problem is that I haven't been able to figure out how to do this conditionally (e.g., with "if #available(iOS 8.0, *)" ) because the variables "searchController" and "controller" are defined outside of a method or function and so can't be assigned conditionally. I guess conditional statements can only be used within functions and methods? (still learning, as you can see).
So can anyone offer a way for me to conditionally disable the search bar here so I can target iOS 7? My code for this class is below. Thanks!
class RegData2: UITableViewController, UISearchResultsUpdating {
let model = Model()
var prevArray = [String]()
var selectionPrev = String()
var filteredTableData = [String]()
var searchController = UISearchController()
let controller = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.searchController = ({
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
self.definesPresentationContext = true
return controller
})()
// Reload the table
self.tableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.searchController.active) && (controller.searchBar.text != "") {
return self.filteredTableData.count
}
else {
return prevArray.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell!
cell.textLabel?.font = UIFont.boldSystemFontOfSize(18)
if (self.searchController.active) && (controller.searchBar.text != "") {
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
}
else {
cell.textLabel?.text = prevArray[indexPath.row]
return cell
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
controller.searchBar.resignFirstResponder()
performSegueWithIdentifier("regData2ToRegView", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "regData2ToRegView" {
let regView = segue.destinationViewController as! RegView
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
var selection : String = prevArray[indexPath.row]
if (self.searchController.active) && (self.controller.searchBar.text != "") {
selection = self.filteredTableData[indexPath.row]
}
else {
selection = self.prevArray[indexPath.row]
}
regView.prevSelection = selection
regView.prevSelectionType = selectionPrev
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
if searchController.searchBar.text != ""{
filteredTableData.removeAll(keepCapacity: true)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
if self.selectionPrev != "ed" {
let array = (self.prevArray as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
// edAlert = 0
} else {
let array = (self.prevArray as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
// edAlert = 1
}
self.tableView.reloadData()
} else {
self.tableView.reloadData()
}
}
}
To check version you can do this:
switch UIDevice.currentDevice().systemVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch) {
case .OrderedSame, .OrderedDescending:
println("iOS >= 8.0")
case .OrderedAscending:
println("iOS < 8.0")
}
But most likely you just need to check if the class is available and then instantiate it:
if NSClassFromString("UISearchController") != nil {
// init and use ...
}
This is called "weakly linked" class.
EDIT:
Also you can use preprocessor to include/exclude any code before compilation, i.e.:
#if __IPHONE_8_0
class RegData2: UITableViewController, UISearchResultsUpdating {
#else
class RegData2: UITableViewController {
#endif
More info here and here and also here.

Tutorial in retrieving, mutating and saving array from Parse.com in Swift with UITableView

import UIKit
class FeedTableViewController: UITableViewController {
var navBar:UINavigationBar=UINavigationBar()
let font = UIFont(name: "Baskerville", size: 15)
var feedData:NSMutableArray = NSMutableArray()
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
#IBAction func likeButton(sender: AnyObject) {
if var votes:Int? = quote!.objectForKey("votes") as? Int {
votes!++
}
}
#IBAction func loadData(sender: AnyObject?) {
feedData.removeAllObjects()
var findFeedData:PFQuery = PFQuery(className: "userQuotes")
findFeedData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]?, error:NSError?)->Void in
if error == nil{
if let objs = objects{
for object in objs{
let quote:PFObject = object as! PFObject
self.feedData.addObject(quote)
// let user:PFUser = (object as! NSArray).lastObject as! PFUser
}
//println(self.feedData)
let array:NSArray = self.feedData.reverseObjectEnumerator().allObjects
self.feedData = NSMutableArray(array: array)
NSOperationQueue.mainQueue().addOperationWithBlock({
self.tableView.reloadData()
})
}
}
}
}
override func viewDidAppear(animated: Bool) {
self.loadData( nil )
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Quotezilla"
// 3
//self.navigationItem.setRightBarButtonItem(rightSearchBarButtonItem, animated: true)
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return feedData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:QuoteTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! QuoteTableViewCell
let quote:PFObject = self.feedData.objectAtIndex(indexPath.row) as! PFObject
cell.contentTextView!.font = font
cell.timeStampLabel!.font = font
cell.publisherLabel!.font = font
cell.contentTextView.alpha = 0
cell.timeStampLabel.alpha = 0
cell.publisherLabel.alpha = 0
cell.contentTextView.text = quote.objectForKey("content") as! String
//cell.publisherLabel.text = quote.objectForKey("publisher") as? String
/* func loadLikes(){
if var votes:Int? = quote.objectForKey("votes") as? Int {
votes!++
}
}*/
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE, MMM d, h:mm a"
cell.timeStampLabel.text = dateFormatter.stringFromDate(quote.createdAt!)
var votes:Int? = quote["votes"] as? Int
if votes == nil {
votes = 0
}
cell.likesLabel?.text = "\(votes!)"
var myObject = quote["publisher"] as? PFObject
myObject?.fetchIfNeeded()
if let foundUser = myObject as? PFUser{
cell.publisherLabel.text = foundUser.username
UIView.animateWithDuration(0.7, animations: {
cell.contentTextView.alpha = 1
cell.timeStampLabel.alpha = 1
cell.publisherLabel.alpha = 1
})
}
return cell
}
So what I am essentially attempting to do is create a likes or votes button. As you see in the code I have a likeButton action that is supposed to auto-increment the likes section in parse. I display the current likes that I have filled into the rows in Parse itself in the cellForRowAtIndexPath function. The problem is that I cannot call quote.objectForKey("votes"), because I initialize it later. I have been poring over this problem and cannot find a way to make the votes update in parse through the likeButton action.
You must live with life on the network. That means your table won't have certain data available when the App starts. Handle a missing object or missing key within a particular cell gracefully and just use some kind of placeholder value. When the parse callback executes, you are already correctly forcing a refresh.
OK So BIG EDIT
This class needed a lot of work. I'm not even going to spell out every change here, but it's basically a complete Parse.com tutorial at this point.
This code compiles cleanly but I can't be sure of everything in your context. In particular do you have a 'likesButton' on every table row as part of your custom table cell view? I'm assuming that.
class FeedTableViewController: UITableViewController {
var navBar = UINavigationBar()
let font = UIFont(name: "Baskerville", size: 15)
var feedData = [PFObject]()
static let cellID = "cell"
// NOTE! See how this tag is set below
#IBAction func likeButton(sender: UIButton) {
let quote = feedData[sender.tag]
if let votes = quote.objectForKey("votes") as? Int {
quote.setObject(votes + 1, forKey: "votes")
}
else {
// CHALLENGE FOR YOU: handle the case of no votes attribute
}
// UPDATE the local UI
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: sender.tag, inSection: 0)],
withRowAnimation: .None)
// CHALLENGE FOR YOU: UPDATE Parse...start a new question if necessary
}
#IBAction func loadData(sender: AnyObject?) {
feedData.removeAll()
PFQuery(className: "userQuotes").findObjectsInBackgroundWithBlock {
[unowned self]
(objects: [AnyObject]?, error: NSError?) -> Void in
if let objs = objects {
for object in objs {
self.feedData.append(object as! PFObject)
}
self.feedData = self.feedData.reverse()
}
NSOperationQueue.mainQueue().addOperationWithBlock { self.tableView.reloadData() }
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.loadData(nil)
self.title = "Quotezilla"
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(FeedTableViewController.cellID, forIndexPath: indexPath) as! QuoteTableViewCell
cell.likesButton!.tag = indexPath.row // See how tag works with the above
cell.contentTextView!.font = font
cell.timeStampLabel!.font = font
cell.publisherLabel!.font = font
cell.contentTextView.alpha = 0.0
cell.timeStampLabel.alpha = 0.0
cell.publisherLabel.alpha = 0.0
let q = feedData[indexPath.row]
if let content = q.objectForKey("content") as? String {
cell.contentTextView.text = content
}
else {
cell.contentTextView.text = "Content not found!"
}
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE, MMM d, h:mm a"
cell.timeStampLabel.text = dateFormatter.stringFromDate(q.createdAt!)
let votes = (q.objectForKey("votes") as? Int) ?? 0
cell.likesLabel?.text = "\(votes)"
let myObject = q.objectForKey("publisher") as? PFObject
myObject?.fetchInBackgroundWithBlock {
[unowned self]
(object: PFObject?, error: NSError?) in
NSOperationQueue.mainQueue().addOperationWithBlock {
if let foundUser = object as? PFUser {
cell.publisherLabel.text = foundUser.username
UIView.animateWithDuration(0.7) {
cell.contentTextView.alpha = 1.0
cell.timeStampLabel.alpha = 1.0
cell.publisherLabel.alpha = 1.0
}
}
else {
cell.publisherLabel.text = "Publisher not found!"
}
}
}
return cell
}
}

How to implement UISearchController in iOS8?

I have tried to implement the UISearchController in IOS8 but failed.
The problem is when I have changed the text and the scope button, noting is presented to me.
And it seems that the updateSearchResultsForSearchController function is not even called when I update the search Bar or the scope button.
Here is my code:
class SearchTestController: UITableViewController, UISearchResultsUpdating {
struct Candy {
let category : String
let name : String
}
var searchcontroller = UISearchController(searchResultsController: nil)
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredcandy = candies.filter() { (candy:Candy) -> Bool in
let scopetest = ( self.category[self.searchcontroller.searchBar.selectedScopeButtonIndex] == "All" ) || ( candy.category == self.category[self.searchcontroller.searchBar.selectedScopeButtonIndex] )
//let texttest = candy.name.rangeOfString(self.searchcontroller.searchBar.text)
//let result = scopetest && (texttest != nil)
return scopetest
}
println(filteredcandy.count)
self.tableView.reloadData()
}
var candies = [Candy]()
var filteredcandy = [Candy]()
var category = ["Chocolate","Hard","Other","All"]
override func viewDidLoad() {
super.viewDidLoad()
// Sample Data for candyArray
self.candies = [Candy(category:"Chocolate", name:"chocolate Bar"),
Candy(category:"Chocolate", name:"chocolate Chip"),
Candy(category:"Chocolate", name:"dark chocolate"),
Candy(category:"Hard", name:"lollipop"),
Candy(category:"Hard", name:"candy cane"),
Candy(category:"Hard", name:"jaw breaker"),
Candy(category:"Other", name:"caramel"),
Candy(category:"Other", name:"sour chew"),
Candy(category:"Other", name:"gummi bear")]
// Reload the table
self.tableView.reloadData()
self.tableView.tableHeaderView = searchcontroller.searchBar
searchcontroller.searchBar.sizeToFit()
searchcontroller.searchBar.showsSearchResultsButton = true
self.definesPresentationContext = true
searchcontroller.searchBar.scopeButtonTitles = category
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchcontroller.active {
return self.candies.count
} else {
return self.candies.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
var candy : Candy
// Check to see whether the normal table or search results table is being displayed and set the Candy object from the appropriate array
if searchcontroller.active {
candy = filteredcandy[indexPath.row]
} else {
candy = candies[indexPath.row]
}
// Configure the cell
cell.textLabel!.text = candy.name
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
Add the following lines to viewDidLoad()
searchcontroller.searchResultsUpdater = self
searchcontroller.delegate = self
Update:
Add the following line in viewDidLoad()
searchcontroller.searchBar.delegate = self
Then update the search results in searchBar(_:selectedScopeButtonIndexDidChange:)