Swift UiTableView with UISegmentedControl not refreshing rows - swift

I have a UIViewController with a UITableView.
At the top I have a UISegmentedController and then a UITableView.
I want the rows to be sorted when you press the segmented controls. Everything is working fine, the rows get displayed properly, but when I press the segmented control button, I call a method that sorts the array that has been displayed in the table view, but they don't get refreshed in the view unless I don't scroll to the bottom and to the top. I call the method tableView.reloadData() after sorting this array, but it doesn't update the tableview.
Here is my code:
import UIKit
import EventKit
extension RangeReplaceableCollectionType where Generator.Element : Equatable {
mutating func removeObject(object : Generator.Element) {
if let index = self.indexOf(object) {
self.removeAtIndex(index)
}
}
}
class EventsViewController: UIViewController {
var calendarName:String!
var currentStat = Stat()
var eventStore = EKEventStore() //represents the Calendar database. Point of contact for accessing calendar
var icloudEventSource: EKSource? //represents the account that a calendar belongs to.
var startDate=NSDate().dateByAddingTimeInterval(-31556926)
var endDate=NSDate().dateByAddingTimeInterval(31556926)
var yourCalendar: EKCalendar?
var calendar: EKCalendar? //represents a calendar in Event Kit
var numberOfCalendars : Int = 0
var calendarsArray = NSMutableArray()
var calendarsPrueba : [EKCalendar]?
var calendarioSeleccionado:String!
var delegate: passDataBackDelegate?
var events : [EKEvent]!
var tableView : UITableView?
var selectedMarks = [StatEvents]()
var selectedIndex = [NSIndexPath]()
var orderedStatEvents = [StatEvents]()
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var textLabel: UILabel!
#IBAction func indexChanged(sender: UISegmentedControl) {
switch segmentedControl.selectedSegmentIndex {
case 0:
orderTable(0)
case 1:
orderTable(1)
default:
break;
}
}
func orderTable(order: Int) {
switch order {
case 0:
currentStat.statEvents.sortInPlace({ $0.name.compare($1.name) == NSComparisonResult.OrderedAscending })
case 1:
currentStat.statEvents.sortInPlace({ $0.name.compare($1.name) == NSComparisonResult.OrderedDescending })
default:
break;
}
tableView?.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
eventStore.requestAccessToEntityType(EKEntityType.Event,
completion: {(granted: Bool, error:NSError?) in
if !granted {
print("Access to store not granted")
}
})
// Buscamos la cuenta de iCloud que contiene los calendarios
for source in eventStore.sources{
if source.sourceType.rawValue == EKSourceType.CalDAV.rawValue && source.title.lowercaseString == "icloud"{
icloudEventSource = source
}
}
///////Mostramos solo calendarios de iCloud//////
if (icloudEventSource != nil){
let calendars = icloudEventSource!.calendarsForEntityType(EKEntityType.Event)
print("1 The iCloud event source was found = \(icloudEventSource!.title)")
print("1 Number of calendars = \(calendars.count)")
for calendars in calendars{
yourCalendar = (calendars as EKCalendar)
print(yourCalendar!.title)
calendarsArray.addObject(yourCalendar!.title)
}
}else{
print("Could not find the iCloud event source")
}
//////Mostramos todos los calendarios///////
let calendars = eventStore.calendarsForEntityType(EKEntityType.Event) //Devuelve los calendarios que son eventos
numberOfCalendars = calendars.count
print("Number of calendars = \(calendars.count)")
for calendars in calendars as [EKCalendar] {
print("events = \(calendars.title)")
calendarsArray.addObject(calendars.title)
}
func calendarEventsWithName( name:String ) -> [EKCalendar]? {
let calendars = eventStore.calendarsForEntityType(EKEntityType.Event) as [EKCalendar]
for cal in calendars {
if cal.title == name {
return [cal]
}
}
print ("failed to find calendar")
return nil
}
let predicate = eventStore.predicateForEventsWithStartDate(startDate, endDate: endDate, calendars: calendarEventsWithName(currentStat.statCalendar))
print("startDate:\(startDate) endDate:\(endDate)")
events = eventStore.eventsMatchingPredicate(predicate) as [EKEvent]!
let calendar = NSCalendar.currentCalendar()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy" //format style. Browse online to get a format that fits your needs.
if events != nil {
print(events.count)
for i in events {
let components = calendar.components(.Hour, fromDate: i.startDate, toDate: i.endDate, options: [])
currentStat.statEvents.append(StatEvents(name: i.title, dateRanges: [i.startDate, i.endDate], hours: components.hour))
}
} else {
print("No hay eventos en este calendario")
}
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
tableView.allowsMultipleSelection = true
cell.textLabel?.font = UIFont.systemFontOfSize(8.0)
cell.textLabel?.text = "\(currentStat.statEvents[indexPath.row].name) \(currentStat.statEvents[indexPath.row].dateRanges) horas=\(currentStat.statEvents[indexPath.row].hours)"
if let selectedPaths = tableView.indexPathsForSelectedRows {
let selected = selectedPaths.filter(){ $0 == indexPath }
if selected.count > 0 {
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None
}
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (selectedMarks.contains(currentStat.statEvents[indexPath.row])){
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = .None
}
print("borro")
selectedMarks.removeObject(currentStat.statEvents[indexPath.row])
} else {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = .Checkmark
}
print("añado")
selectedMarks.append(currentStat.statEvents[indexPath.row]) //add the object to selectedMarks
}
selectedIndex = tableView.indexPathsForSelectedRows!
tableView.reloadData()
}
#IBAction func done(sender: AnyObject) {
selectedMarks.sortInPlace({ $0.dateRanges[0].compare($1.dateRanges[0]) == NSComparisonResult.OrderedAscending })
currentStat.statEvents = selectedMarks
navigationController!.popViewControllerAnimated(true)
}
}

The only way how I can imagine it is happening — tableView is nil in a orderTable call.
I can not find anywhere in the code assignment of table to a tableView. How is it initialized? It also worth to mention that tableView in delegate and dataSource methods a local parameter of methods, not an instance variable.
Mark it as IBOutlet and set it in IB as you do with textLabel and segmentedControl or you can change UIViewController to UITableViewController and update IB as well.

Related

Retrieve only 5 users at a time :Firebase [like Instagram]

For the past few days, I have been trying to create an Instagram-like feed for my app. To be more specific: load new posts (5) each time the user updates the feed from the bottom.
I am currently using Firebase to both store and display my data.
My code so far, looks like this:
var ref:FIRDatabaseReference!
var dict = [String:Any]()
var posts = [[String:Any]]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
ref = FIRDatabase.database().reference()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
loadValues()
}
func loadValues() {
dict.removeAll()
posts.removeAll()
ref.child("posts").queryOrderedByChild("timeCreated").queryLimitedToLast(5).observeEventType(.ChildAdded) { (snapshot:FIRDataSnapshot) in
if let timeCreated = snapshot.value!["timeCreated"] as? Int {
self.dict["timeCreated"] = timeCreated
}
if let postText = snapshot.value!["postText"] as? String {
self.dict["postText"] = postText
}
self.posts.append(self.dict)
self.tableView.reloadData()
}
}
func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height {
//tableView.tableFooterView!.hidden = true
let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
pagingSpinner.startAnimating()
pagingSpinner.hidesWhenStopped = true
pagingSpinner.sizeToFit()
tableView.tableFooterView = pagingSpinner
//loadMore(5)
} else {
let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
pagingSpinner.stopAnimating()
pagingSpinner.hidesWhenStopped = true
pagingSpinner.sizeToFit()
pagingSpinner.hidden = true
tableView.tableFooterView = pagingSpinner
tableView.tableFooterView?.hidden = true
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
if let postText = posts[indexPath.row]["postText"] as? String {
cell.textLabel!.text = postText
}
return cell
}
func loadMore(increment:Int) {
//What should go in here?
}
So what I am trying to do here - is that I am detecting when the user has scrolled to the bottom (in my scrollViewDidScroll function. Then I am sinply displaying the activity indicator, and calling the function loadMore(5) where 5 is the amount of new posts that I want to display.
So here I have two problems. The timeCreated variable is simply a timestamp, where I have ten records (1-10, where 10 is the newest, and 1 is the oldest). With this code that I have now, the tableView displays the data in an ascending view, starting at 5 and ending at 10.
I have tried to reverse the array of dictionaries (post) by simply doing a .reverse() before appending the dict in the loadValues function. as I simply want it to display 10 at the top, and 5 at the bottom.
The second problem I have is that I can't really seem to find a good and effective way of updating the tableView (adding another 5 records). I have tried to simply just have a global variable with the default value of 5, and then on loadMore simply plus it by five, and then do a removeAll() on both the dict and posts - with no luck (the tableView scrolls to the top, which I don't want to). I have also tried to play with both queryLimitedTolast and queryLimitedToFirst where I ended up duplicating some data.
So in other words, I also need to check that the user can in fact load 5 new unique posts (or ex. 3, if there's only 3 unique posts left).
Does anyone have any ideas on how I would approach this?
Help would be greatly appreciated, as I have struggled with this for the past two days now.
If you are using tableView update your DataSource instead of adding a row at a particular index. using struct is a common aproach.
struct dataS {
var postData : String!
var index_Initial : Int!
init(post : String!, ind : Int!)
{
self.postData = post
self.index_Initial = ind
}
}
Declare an array of type dataSourceS
var dataFeed= [dataS]()
For knowing that how many posts you have already retrived , you need to keep the index of each post in the the post node itself.Which can be done by counting the no of children in the post node and incrementing it by one.Or creating a complete separate node of
noOfPosts: 100, //Lets say there are 100 posts in your DB
Posts : {
Post1:{
text : asdasdasd,
index : 12
},
Post2:{
text : asdasddasdasd,
index : 13
},.....
}
Your final code will look something like this:-
import UIKit
import Firebase
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var dataFeed = [dataS]()
let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
var totalNoOfPost : Int!
#IBOutlet weak var customTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
customTableView.delegate = self
customTableView.dataSource = self
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
FIRDatabase.database().reference().child("Posts").observeSingleEventOfType(.Value, withBlock: {(snap) in
if let postDict = snap.value as? [String:AnyObject]{
self.totalNoOfPost = postDict.count
self.loadMore()
}
})
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataFeed.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = customTableView.dequeueReusableCellWithIdentifier("customCell") as! customTableViewCell
if dataFeed.count > 0{
cell.poatLabel.text = dataFeed[indexPath.row].postData
}
return cell
}
func loadMore(){
let initialFeedCount : Int = dataFeed.count
if totalNoOfPost - initialFeedCount - 4 > 0{
FIRDatabase.database().reference().child("Posts").queryOrderedByChild("index").queryStartingAtValue(totalNoOfPost - initialFeedCount - 4).queryEndingAtValue(totalNoOfPost - initialFeedCount).observeEventType(.Value, withBlock: {(recievedSnap) in
if recievedSnap.exists(){
for each in recievedSnap.value as! [String:AnyObject]{
let temp = dataS.init(post: each.1["text"] as! String, ind : each.1["index"] as! Int)
self.dataFeed.insert(temp, atIndex: 5 * Int(self.dataFeed.count/5))
self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
if self.dataFeed.count == initialFeedCount+5{
self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
self.customTableView.reloadData()
}
}
}
}, withCancelBlock: {(err) in
print(err.localizedDescription)
})
}else if totalNoOfPost - initialFeedCount - 4 <= 0{
FIRDatabase.database().reference().child("Posts").queryOrderedByChild("index").queryStartingAtValue(0).queryEndingAtValue(totalNoOfPost - initialFeedCount).observeEventType(.Value, withBlock: {(recievedSnap) in
if recievedSnap.exists(){
for each in recievedSnap.value as! [String:AnyObject]{
let temp = dataS.init(post: each.1["text"] as! String, ind : each.1["index"] as! Int)
self.dataFeed.insert(temp, atIndex: 5 * Int(self.dataFeed.count/5))
self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
if self.dataFeed.count == initialFeedCount+4{
self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
self.customTableView.reloadData()
self.pagingSpinner.stopAnimating()
}
}
}else{
self.pagingSpinner.stopAnimating()
}
}, withCancelBlock: {(err) in
print(err.localizedDescription)
})
}
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if (indexPath.row + 1) == dataFeed.count {
print("Displayed the last row!")
pagingSpinner.startAnimating()
pagingSpinner.hidesWhenStopped = true
pagingSpinner.sizeToFit()
customTableView.tableFooterView = pagingSpinner
loadMore()
}
}
}
struct dataS {
var postData : String!
var index_Initial : Int!
init(post : String!, ind : Int!)
{
self.postData = post
self.index_Initial = ind
}
}

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
}
}

Swift's deinit is not called

private let DBItemCellIdentifier = "ItemCellIdentifier"
private let DBItemSegueIdentifier = "ItemSegueIdentifier"
class DBItemsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DBItemTableViewCellDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var previousButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var categoryNameLabel: UILabel!
private var elements = [Any]()
private var currentItemIndex = 0
private var isFetching = false
private weak var currentCategory: DBCategory? {
didSet {
updateView()
}
}
var categories = [DBCategory]()
var currentCategoryIndex = 0
//MARK: - Class Methods
//MARK: - Initialization
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100.0
tableView.tableFooterView = UIView(frame: CGRectZero)
setupUserAndCartButtons()
fetchItems()
}
deinit {
print("deinit")
}
//MARK: - Actions
#IBAction func nextButtonTapped(sender: UIButton) {
currentCategoryIndex = min(currentCategoryIndex + 1, categories.count - 1)
fetchItems()
}
#IBAction func previousButtonTapped(sender: UIButton) {
currentCategoryIndex = max(currentCategoryIndex - 1, 0)
fetchItems()
}
//MARK: - Private
private func fetchItems() {
tableView.alpha = 0
currentCategory = nil
if !categories.isEmpty && !isFetching {
let category = categories[currentCategoryIndex]
currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
if currentCategory == nil {
SVProgressHUD.show()
}
isFetching = true
DBNetworkClient.sharedClient().itemsForCategory(category, completionBlock: { error in
defer {
self.isFetching = false
SVProgressHUD.dismiss()
UIAlertController.showAlertFromError(error)
}
self.currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
})
}
}
private func updateView() {
let category = categories[currentCategoryIndex]
title = category.menu.location.name
categoryNameLabel.text = category.name
previousButton.hidden = currentCategoryIndex == 0 ? true : false
nextButton.hidden = currentCategoryIndex == categories.count - 1 ? true : false
prepareElements()
tableView.reloadData()
UIView.animateWithDuration(0.5, animations: {
self.tableView.alpha = 1
})
}
private func prepareElements() {
elements.removeAll(keepCapacity: false)
if let items = currentCategory?.items {
for item in items {
elements.append(item)
}
}
if let sets = currentCategory?.sets {
for set in sets {
elements.append(set)
}
}
elements.sortInPlace {
let left = ($0 as? DBSet)?.position ?? ($0 as? DBItem)?.position
let right = ($1 as? DBSet)?.position ?? ($1 as? DBItem)?.position
return left < right
}
}
//MARK: - Overridden
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let element = elements[currentItemIndex]
if segue.identifier == DBItemSegueIdentifier {
let itemViewController = segue.destinationViewController as! DBItemViewController
itemViewController.prepareWithElement(element)
}
}
//MARK: - UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0 //when I change to elements.count, deinit is not called
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
let element = elements[indexPath.row]
if let item = element as? DBItem {
cell.configureCellWithItem(item)
} else if let set = element as? DBSet {
cell.configureCellWithSet(set)
}
cell.delegate = self
return cell
}
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
currentItemIndex = indexPath.row
performSegueWithIdentifier(DBItemSegueIdentifier, sender: tableView.cellForRowAtIndexPath(indexPath))
}
//MARK: - DBItemTableViewCellDelegate
func itemTableViewCell(cell: DBItemTableViewCell, willPresentSetGroupsViewControllerForSet set: DBSet) {
presentSetOrderControllerWithOrder(DBSetOrder(set: set))
}
func itemTableViewCell(cell: DBItemTableViewCell, willPresentItemMealSizesViewControllerForItem item: DBItem) {
presentItemOrderControllerWithOrder(DBItemOrder(item: item))
}
}
Why my deinit is not called. I will offer 100 bounty once I will be able to do this, and award to that one, who help me solve this problem... I will offer a bounty even after solving the problem.
VERY IMPORTANT INFO:
this code calls deinit. IT IS WORKING. Because number of rows is 0. But I need to have there elements.count. When I change to this, deinit is not called.
EDIT:
func itemsForCategory(category: DBCategory, completionBlock: DBErrorHandler) {
let query = "locations/" + category.menu.location.identifier + "/categories/" + category.identifier
GET(query, parameters: nil, success: { operation, response in
if let error = NSError(response: response) {
completionBlock(error)
} else {
self.coreDataAssistant.parseAndSaveItemsToPersistentStore(response as? NSDictionary, completionBlock: { error in
completionBlock(error)
})
}
}) { operation, error in
let responseError = NSError(response: operation.responseObject)
completionBlock(responseError ?? error)
}
}
You are assigning self as your table view cell's delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
let element = elements[indexPath.row]
if let item = element as? DBItem {
cell.configureCellWithItem(item)
} else if let set = element as? DBSet {
cell.configureCellWithSet(set)
}
// HERE
cell.delegate = self
return cell
}
The cell's delegate property is defined as follows:
var delegate: DBItemTableViewCellDelegate?
This creates a strong reference between the cell and the delegate (your view controller). The cell is also retained by the table view. This creates a retain cycle.
You will need to change the definition of the delegate property to be weak:
weak var delegate: DBItemTableViewCellDelegate?
Edit based on comment:
Your DBItemTableViewCellDelegate definition will need to be defined as a class-only protocol
protocol DBItemTableViewCellDelegate: class {
...
}

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:)