Swift
I'm trying to make it so when you pull to refresh on the tableview it updates the tableview with the data that is stored on Parse.com
I've researched this and it seems I need to use loadObjects()
When I place this in it says: "Use of unresolved identifier 'loadObjects'
Not sure if this is what I need or not..
Here is my ViewController.swift
import UIKit
import Parse
import Bolts
import ParseUI
class EventsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var timer: NSTimer!
var isAnimating = false
var currentColorIndex = 0
var currentLabelIndex = 0
var customView: UIView!
var labelsArray: Array<UILabel> = []
var refreshControl: UIRefreshControl!
var testArray = [String]()
var subArray = [String]()
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
refreshControl = UIRefreshControl()
tableview.addSubview(refreshControl)
loadCustomRefreshContents()
//refreshControl colors
refreshControl.backgroundColor = UIColor.clearColor() //color of background
refreshControl.tintColor = UIColor.clearColor() //color of indicator
let query = PFQuery(className: "events")
let runkey = query.orderByDescending("eventTitle")
runkey.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error:NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
let load = object.objectForKey("eventTitle") as! String
self.testArray.append(load)
let subload = object.objectForKey("date") as! String
self.subArray.append(subload)
print(self.testArray)
}
}
} else {
print("error:\(error!) \(error!.userInfo)")
}
}
sleep(3)
do_table_refresh()
loadViewIfNeeded()
loadObjects()
/*let url = NSURL(string: "https://m.facebook.com/CathedralCityMunicipal")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {
(data, response, error) in
if error == nil {
let urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(urlContent)
dispatch_async(dispatch_get_main_queue()) {
self.webView.loadHTMLString(urlContent! as String, baseURL: nil)
}
}
}
task.resume()*/
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func do_table_refresh() {
dispatch_async(dispatch_get_main_queue()) {
self.tableview.reloadData()
return
}
}
func tableView(tableView:UITableView!, numberOfRowsInSection section:Int) -> Int {
return testArray.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("eventcell", forIndexPath: indexPath) as! EventTableViewCell
cell.title.text = self.testArray[indexPath.row]
cell.subTitle.text = self.subArray[indexPath.row]
return cell
}
//refreshes tableview; starts refresh
func loadCustomRefreshContents() {
let refreshContents = NSBundle.mainBundle().loadNibNamed("RefreshControl", owner: self, options: nil)
customView = refreshContents[0] as! UIView
customView.frame = refreshControl.bounds
for var i=0; i<customView.subviews.count; ++i {
labelsArray.append(customView.viewWithTag(i + 1) as! UILabel)
}
refreshControl.addSubview(customView)
do_table_refresh()
}
//stops refresh
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
do_table_refresh()
if refreshControl.refreshing {
if !isAnimating {
doSomething()
animateRefreshStep1()
self.do_table_refresh()
}
}
}
//cycles through colors
func getNextColor() -> UIColor {
var colorsArray: Array<UIColor> = [UIColor.magentaColor(), UIColor.brownColor(), UIColor.yellowColor(), UIColor.redColor(), UIColor.greenColor(), UIColor.blueColor(), UIColor.orangeColor()]
if currentColorIndex == colorsArray.count {
currentColorIndex = 0
}
let returnColor = colorsArray[currentColorIndex]
++currentColorIndex
return returnColor
}
func doSomething() {
timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: "endOfWork", userInfo: nil, repeats: true)
self.do_table_refresh()
}
func endOfWork() {
refreshControl.endRefreshing()
self.do_table_refresh()
timer.invalidate()
timer = nil
}
//first part of animation
func animateRefreshStep1() {
isAnimating = true
UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
self.labelsArray[self.currentLabelIndex].textColor = self.getNextColor()
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformIdentity
self.labelsArray[self.currentLabelIndex].textColor = UIColor.blackColor()
}, completion: { (finished) -> Void in
++self.currentLabelIndex
if self.currentLabelIndex < self.labelsArray.count {
self.animateRefreshStep1()
}
else {
self.animateRefreshStep2()
}
})
})
}
//second part of animation
func animateRefreshStep2() {
UIView.animateWithDuration(0.35, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[0].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[1].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[2].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[3].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[4].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[5].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[6].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[7].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[8].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[9].transform = CGAffineTransformMakeScale(1.5, 1.5)
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[0].transform = CGAffineTransformIdentity
self.labelsArray[1].transform = CGAffineTransformIdentity
self.labelsArray[2].transform = CGAffineTransformIdentity
self.labelsArray[3].transform = CGAffineTransformIdentity
self.labelsArray[4].transform = CGAffineTransformIdentity
self.labelsArray[5].transform = CGAffineTransformIdentity
self.labelsArray[6].transform = CGAffineTransformIdentity
self.labelsArray[7].transform = CGAffineTransformIdentity
self.labelsArray[8].transform = CGAffineTransformIdentity
self.labelsArray[9].transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
if self.refreshControl.refreshing {
self.currentLabelIndex = 0
self.animateRefreshStep1()
}
else {
self.isAnimating = false
self.currentLabelIndex = 0
for var i=0; i<self.labelsArray.count; ++i {
self.labelsArray[i].textColor = UIColor.blackColor()
self.labelsArray[i].transform = CGAffineTransformIdentity
}
}
})
})
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I have tried letting the do_table_refresh() function run after the refresh is over, but the tableview does not update. I have also tried using self.tableview.reloadData() but also does not work.
I'm not sure what I am doing wrong, any help is greatly appreciated!
Feel free to ask me for any additional information!
Thank you for your time!
So, if you are trying to get the Parse data everytime you to pull-to-refresh you should query for them always.
Keep your query into a single function and call a tableView.reloadData() so it will reload the tableView every time you query your events:
func loadEvents () -> Void {
self.testArray = []
self.subArray = []
let query = PFQuery(className: "events")
let runkey = query.orderByDescending("eventTitle")
runkey.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error:NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
let load = object.objectForKey("eventTitle") as! String
self.testArray.append(load)
let subload = object.objectForKey("date") as! String
self.subArray.append(subload)
//reload TableView
self.tableView.reloadData()
print(self.testArray)
}
}
} else {
print("error:\(error!) \(error!.userInfo)")
}
}
refreshControl.endRefreshing()
}
And then add a Target for your UIRefreshControl inside your ViewDidLoad()
refreshControl.addTarget(self, action: Selector("loadEvents"), forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
Also, you must declare your UIRefreshControl as follows:
var refreshControl = UIRefreshControl()
Related
I have a chat and when i scroll down to fetch older messages i want the collectionView to stay still and allow the user to manually scroll the older message who just loaded like in messenger. With my collectionView it automatically scroll to the top of the new items. I have added a method inside my scrollViewDidEndDragging but there is 2 problems:
It's glitchy meaning it scroll to the top of the new data then scroll back to the "previous position".
It's not exactly moving back to the previous position, the oldest message before loading the older data is not as the same place, it's almost in the center instead of staying on top. Here's my code:
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let contentOffset = scrollView.contentOffset.y
if contentOffset <= -40 {
self.collectionView.refreshControl?.beginRefreshing()
self.fetchMessages()
let beforeTableViewContentHeight = collectionView.contentSize.height
let beforeTableViewOffset = collectionView.contentOffset.y
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.collectionView.layer.layoutIfNeeded()
let insertCellHeight = beforeTableViewOffset + (self.collectionView.contentSize.height - beforeTableViewContentHeight)
let newOffSet = CGPoint(x: 0, y: insertCellHeight)
self.collectionView.contentOffset = newOffSet
}
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension RoomMessageViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .init(top: 16, left: 0, bottom: 16, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let estimatedSizeCell = RoomMessageCell(frame: frame)
estimatedSizeCell.roomMessage = chatMessages[indexPath.section][indexPath.row]
estimatedSizeCell.layoutIfNeeded()
let targetSize = CGSize(width: view.frame.width, height: 1000)
let estimatedSize = estimatedSizeCell.systemLayoutSizeFitting(targetSize)
return CGSize(width: view.frame.width, height: estimatedSize.height)
}
}
[Updated code#1]
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y <= -50 {
self.collectionView.reloadData()
self.fetchMessages()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
let previousContentSize = self.collectionView.contentSize
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.collectionViewLayout.prepare()
self.collectionView.layoutIfNeeded()
let newContentSize = self.collectionView.contentSize
print("previous Content Size \(previousContentSize) and new Content Size \(newContentSize)")
let contentOffset = newContentSize.height - previousContentSize.height
self.collectionView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false)
}
}
}
[Updated Code#2]
var lastDocumentSnapshot: DocumentSnapshot!
var isScrollBottom = false
var isFirstLoad = true
var isLoading = false
private var messages = [RoomMessage]()
private var chatMessages = [[RoomMessage]]()
override func viewDidAppear(_ animated: Bool) {
self.isScrollBottom = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !isScrollBottom {
DispatchQueue.main.async(execute: {
self.collectionView.scrollToBottom(animated: false)
self.isScrollBottom = true
self.isFirstLoad = false
})
}
}
func fetchMessages() {
var query: Query!
guard let room = room else{return}
guard let roomID = room.recentMessage.roomID else{return}
collectionView.refreshControl?.beginRefreshing()
if messages.isEmpty {
query = COLLECTION_ROOMS.document(roomID).collection("messages").order(by: "timestamp", descending: false).limit(toLast: 15)
print("First 15 msg loaded")
} else {
query = COLLECTION_ROOMS.document(roomID).collection("messages").order(by: "timestamp", descending: false).end(beforeDocument: lastDocumentSnapshot).limit(toLast: 15)
print("Next 15 msg loaded")
}
query.addSnapshotListener { (snapshot, err) in
if let err = err {
print("\(err.localizedDescription)")
} else if snapshot!.isEmpty {
self.collectionView.refreshControl?.endRefreshing()
return
}
guard let lastSnap = snapshot?.documents.first else {return}
self.lastDocumentSnapshot = lastSnap
snapshot?.documentChanges.forEach({ (change) in
if change.type == .added {
let dictionary = change.document.data()
let timestamp = dictionary["timestamp"] as? Timestamp
var message = RoomMessage(dictionary: dictionary)
let date = timestamp?.dateValue()
let formatter1 = DateFormatter()
// formatter1.dateStyle = .medium
formatter1.timeStyle = .short
message.timestampHour = formatter1.string(from: date!)
message.timestampDate = date!
self.messages.append(message)
self.messages.sort(by: { $0.timeStamp.compare($1.timeStamp) == .orderedAscending })
}
self.attemptToAssembleGroupedMessages { (assembled) in
if assembled {
}
}
})
self.collectionView.refreshControl?.endRefreshing()
self.lastDocumentSnapshot = snapshot?.documents.first
}
}
// MARK: - Helpers
func configureUI() {
// collectionView.alwaysBounceVertical = true
self.hideKeyboardOnTap()
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionHeadersPinToVisibleBounds = true
}
let refreshControl = UIRefreshControl()
collectionView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
}
fileprivate func attemptToAssembleGroupedMessages(completion: (Bool) -> ()){
chatMessages.removeAll()
let groupedMessages = Dictionary(grouping: messages) { (element) -> Date in
return element.timestampDate.reduceToMonthDayYear() }
// provide a sorting for the keys
let sortedKeys = groupedMessages.keys.sorted()
sortedKeys.forEach { (key) in
let values = groupedMessages[key]
chatMessages.append(values ?? [])
self.collectionView.reloadData()
}
completion(true)
}
#objc func handleRefresh() {
}
}
extension RoomMessageViewController {
//NEKLAS METHOD
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let contentOffset = scrollView.contentOffset.y
var lastContentSize = scrollView.contentSize
var currentOffset = scrollView.contentOffset
if contentOffset <= -50 {
self.isLoading = true
self.collectionView.reloadData()
self.fetchMessages()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
self.collectionView.layer.layoutIfNeeded()
let newContentSize = self.collectionView.contentSize
let delta = newContentSize.height - lastContentSize.height
lastContentSize = self.collectionView.contentSize
if delta > 0 {
currentOffset.y = currentOffset.y + delta
self.collectionView.setContentOffset(currentOffset, animated: false)
if self.isLoading { return }
self.isLoading = false
}
}
}
}
//ANOTHER METHOD (More precise but has some bug)
// override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// if scrollView.contentOffset.y <= -50 {
// self.collectionView.reloadData()
// self.fetchMessages()
// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
// let previousContentSize = self.collectionView.contentSize
// self.collectionView.collectionViewLayout.invalidateLayout()
// self.collectionView.collectionViewLayout.prepare()
// self.collectionView.layoutIfNeeded()
// let newContentSize = self.collectionView.contentSize
// print("previous Content Size \(previousContentSize) and new Content Size \(newContentSize)")
// let contentOffset = newContentSize.height - previousContentSize.height
// self.collectionView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false)
// }
// }
//}
[Updated Code #3]
var currentOffset: CGPoint = .zero
var lastContentSize: CGSize = .zero
var currentPage = 1
#objc func fetchMessages() {
var query: Query!
guard let room = room else{return}
guard let roomID = room.recentMessage.roomID else{return}
collectionView.refreshControl?.beginRefreshing()
if messages.isEmpty {
query = COLLECTION_ROOMS.document(roomID).collection("messages").order(by: "timestamp", descending: false).limit(toLast: 15)
print("First 15 msg loaded")
} else {
query = COLLECTION_ROOMS.document(roomID).collection("messages").order(by: "timestamp", descending: false).end(beforeDocument: lastDocumentSnapshot).limit(toLast: 15)
print("Next 15 msg loaded")
self.currentPage = self.currentPage + 1
print(self.currentPage)
}
query.addSnapshotListener { (snapshot, err) in
if let err = err {
print("\(err.localizedDescription)")
} else if snapshot!.isEmpty {
self.collectionView.refreshControl?.endRefreshing()
return
}
guard let lastSnap = snapshot?.documents.first else {return}
self.lastDocumentSnapshot = lastSnap
snapshot?.documentChanges.forEach({ (change) in
if change.type == .added {
let dictionary = change.document.data()
let timestamp = dictionary["timestamp"] as? Timestamp
var message = RoomMessage(dictionary: dictionary)
let date = timestamp?.dateValue()
let formatter1 = DateFormatter()
formatter1.timeStyle = .short
message.timestampHour = formatter1.string(from: date!)
message.timestampDate = date!
self.messages.append(message)
self.messages.sort(by: { $0.timeStamp.compare($1.timeStamp) == .orderedAscending })
self.attemptToAssembleGroupedMessages { (assembled) in
if assembled {
}
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(10)) {
if self.currentPage == 1 {
self.collectionView.reloadData()
self.collectionView.scrollToBottom(animated: false)
} else {
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
let newContentSize = self.collectionView.contentSize
let delta = newContentSize.height - self.lastContentSize.height
self.lastContentSize = self.collectionView.contentSize
if delta > 0 {
self.currentOffset.y = self.currentOffset.y + delta
self.collectionView.setContentOffset(self.currentOffset, animated: false)
}
}
self.collectionView.refreshControl?.endRefreshing()
self.lastDocumentSnapshot = snapshot?.documents.first
}
}
})
}
}
fileprivate func attemptToAssembleGroupedMessages(completion: (Bool) -> ()){
chatMessages.removeAll()
let groupedMessages = Dictionary(grouping: messages) { (element) -> Date in
return element.timestampDate.reduceToMonthDayYear() }
// provide a sorting for the keys
let sortedKeys = groupedMessages.keys.sorted()
sortedKeys.forEach { (key) in
let values = groupedMessages[key]
chatMessages.append(values ?? [])
// self.collectionView.reloadData()
}
completion(true)
}
}
extension RoomMessageViewController {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
// We capture any change of offSet/contentSize
self.lastContentSize = scrollView.contentSize
self.currentOffset = scrollView.contentOffset
}
Here are your updated codes:
var isFirstLoad = true
var isLoading = false
// here is place when you get your message after fetching
// THIS IS FIRST LOAD WHEN YOU OPEN SCREEN
// If pageNumber is 1 or isFirstLoad = true
// After fetching data, reloadData and scroll to lastIndex (newest message), must call this to get the final contentSize on firstLoad.
self.tableViewVideo.reloadData()
let lastIndexPath = IndexPath(row: self.listVideo.count - 1, section: 0)
self.tableViewVideo.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
self.isFirstLoad = false // reset it
Next, in your scrollViewDidScroll(scrollView: UIScrollView)
func scrollViewDidScroll(scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
self.lastContentSize = scrollView.contentSize
self.currentOffset = scrollView.contentOffset
if contentOffset <= -50 {
if self.isLoading { return } // Here is the FLAG can help you to avoid spamming scrolling that trigger history loading
self.isLoading = true
self.fetchMessages() // update your list then reloadData, end refreshing, set isLoading = false (reset FLAG)
}
}
After you have new data from history fetching, reloadData(), then now you can do your animation.
This is for history loading.
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
let newContentSize = self.collectionView.contentSize
let delta = newContentSize.height - self.lastContentSize.height
self.lastContentSize = self.tableViewVideo.contentSize
if delta > 0 {
self.currentOffset.y = self.currentOffset.y + delta
self.collectionView.setContentOffset(self.currentOffset, animated: false)
}
self.collectionView.refreshControl?.endRefreshing()
Entire solution with real example:
import UIKit
struct VideoItem {
var thumbnailURL = ""
var videoURL = ""
var name = ""
}
class TaskListScreen: UIViewController {
#IBOutlet weak var tableViewVideo: UITableView!
#IBOutlet weak var labelHost: UILabel!
var listVideo: [VideoItem] = []
var currentOffset: CGPoint = .zero
var lastContentSize: CGSize = .zero
var isLoading = false
var currentPage = 1
override func viewDidLoad() {
super.viewDidLoad()
let refresh = UIRefreshControl()
refresh.tintColor = .red
refresh.addTarget(self, action: #selector(loadHistory), for: .valueChanged)
self.tableViewVideo.refreshControl = refresh
self.setupTableView()
self.initData()
self.showSkeletonLoadingView() // cover your message list
self.loadHistory() // currentPage = 1 mean latest messages
}
private func setupTableView() {
tableViewVideo.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
tableViewVideo.dataSource = self
tableViewVideo.delegate = self
}
#objc func loadHistory() {
let data: [VideoItem] = [
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny"),
.init(thumbnailURL: "https://i.ytimg.com/vi/qk2y-TiLDZo/hqdefault.jpg", videoURL: "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8", name: "Big Buck Bunny")]
// suppose that api takes 2 sec to finish
if self.currentPage == 1 { self.listVideo.removeAll() } // reset for first load
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) { [weak self] in
guard let _self = self else { return }
_self.listVideo.insert(contentsOf: data, at: 0) // update your data source here
if _self.currentPage == 1 {
_self.tableViewVideo.reloadData()
let lastIndexPath = IndexPath(row: _self.listVideo.count - 1, section: 0)
_self.tableViewVideo.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
_self.hideYourSkeletonLoadingView() // hide the cover that is covering your message list
} else {
_self.tableViewVideo.reloadData()
_self.tableViewVideo.layoutIfNeeded()
let newContentSize = _self.tableViewVideo.contentSize
let delta = newContentSize.height - _self.lastContentSize.height
_self.lastContentSize = _self.tableViewVideo.contentSize
if delta > 0 {
_self.currentOffset.y = _self.currentOffset.y + delta
_self.tableViewVideo.setContentOffset(_self.currentOffset, animated: false)
}
_self.tableViewVideo.refreshControl?.endRefreshing()
}
_self.currentPage += 1 // move to next page, for next load
}
}
}
extension TaskListScreen: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listVideo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let item = listVideo[indexPath.item]
cell.labelTitle.text = "Book: \(indexPath.item + 1)" //item.name
return cell
}
}
extension TaskListScreen: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// We capture any change of offSet/contentSize
self.lastContentSize = scrollView.contentSize
self.currentOffset = scrollView.contentOffset
}
}
I try to make a like button and unlike but I it gives me error when I try to press the button in the simulator .if you know any other code for the like button to be much easier will be helpful ( or some websites , yt vids)
#IBOutlet weak var postTextLabel: UILabel!
#IBOutlet weak var subtitleLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var likeLabel : UILabel!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var unlikeBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
profileImageView.layer.cornerRadius = profileImageView.bounds.height / 2
profileImageView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
weak var post:Post?
func set(post:Post) {
self.post = post
var postID : String!
self.profileImageView.image = nil
ImageService.getImage(withURL: post.author.photoURL) { image , url in
guard let _post = self.post else {return}
if _post.author.photoURL.absoluteString == url.absoluteString {
self.profileImageView.image = image
}else {
print("not the right image")
}
}
usernameLabel.text = post.author.username
postTextLabel.text = post.text
subtitleLabel.text = post.createdAt.calenderTimeSinceNow()
}
var postID : String!
#IBAction func likePressed(_ sender: Any) {
self.postID = "post_0"
let ref = Database.database().reference()
let keyToPost = ref.child("posts").childByAutoId().key
ref.child("posts").child(self.postID).observeSingleEvent(of: .value) { (snapshot) in
if let post = snapshot.value as? [String : AnyObject] {
let updateLikes : [ String : Any] = [ "peopleWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid ]
ref.child("posts").child(self.postID).updateChildValues(updateLikes, withCompletionBlock : {(error ,reff) in
if error == nil {
ref.child("posts").child(self.postID).observeSingleEvent(of : .value, with: { (snap) in
if let properties = snap.value as? [ String : AnyObject] {
if let likes = properties["peopleWhoLike"] as? [String: AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
}
}
})
}
})
}
}
}
#IBAction func unlikedPressed(_ sender:Any) {
let ref = Database.database().reference()
ref.child("posts").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
if let properties = snapshot.value as? [String : AnyObject] {
if let peopleWhoLike = properties["peopleWhoLike"] as? [String: AnyObject] {
for (id,person) in peopleWhoLike {
if person as? String == Auth.auth().currentUser!.uid {
ref.child("posts").child(self.postID).child("peopleWhoLike").child(id).removeValue(completionBlock: {(error , reff)in
if error == nil {
ref.child("posts").child(self.postID).observeSingleEvent(of: .value, with: {(snap) in
if let prop = snap.value as? [String : AnyObject] {
if let likes = prop["peopleWhoLike"] as? [String: AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
ref.child("posts").child(self.postID).updateChildValues(["likes" : count])
} else {
self.likeLabel.text = " 0 Likes"
ref.child("posts").child(self.postID).updateChildValues(["likes" : 0])
}
}
})
}
})
self.likeBtn.isHidden = false
self.unlikeBtn.isHidden = true
self.unlikeBtn.isEnabled = true
break
}
}
}
}
})
ref.removeAllObservers()
}
}
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return posts.count
case 1:
return fetchingMore ? 1 : 0
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
var tableView:UITableView!
var cellHeights: [IndexPath : CGFloat] = [:]
var posts = [Post]()
var fetchingMore = false
var endReached = false
let leadingScreensForBatching:CGFloat = 3.0
var refreshControl:UIRefreshControl!
var seeNewPostsButton:SeeNewPostsButton!
var seeNewPostsButtonTopAnchor:NSLayoutConstraint!
var lastUploadedPostID:String?
var postsRef:DatabaseReference {
return Database.database().reference().child("posts")
}
var oldPostsQuery:DatabaseQuery {
var queryRef:DatabaseQuery
let lastPost = posts.last
if lastPost != nil {
let lastTimestamp = lastPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = postsRef.queryOrdered(byChild: "timestamp").queryEnding(atValue: lastTimestamp)
} else {
queryRef = postsRef.queryOrdered(byChild: "timestamp")
}
return queryRef
}
var newPostsQuery:DatabaseQuery {
var queryRef:DatabaseQuery
let firstPost = posts.first
if firstPost != nil {
let firstTimestamp = firstPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = postsRef.queryOrdered(byChild: "timestamp").queryStarting(atValue: firstTimestamp)
} else {
queryRef = postsRef.queryOrdered(byChild: "timestamp")
}
return queryRef
}
#IBAction func handleLogoutButton(_ sender: Any) {
try! Auth.auth().signOut()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
tableView.register(LoadingCell.self, forCellReuseIdentifier: "loadingCell")
tableView.backgroundColor = UIColor(white: 0.90,alpha:1.0)
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
// Fallback on earlier versions
layoutGuide = view.layoutMarginsGuide
}
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
refreshControl = UIRefreshControl()
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
// Fallback on earlier versions
tableView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
seeNewPostsButton = SeeNewPostsButton()
view.addSubview(seeNewPostsButton)
seeNewPostsButton.translatesAutoresizingMaskIntoConstraints = false
seeNewPostsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
seeNewPostsButtonTopAnchor = seeNewPostsButton.topAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: -44)
seeNewPostsButtonTopAnchor.isActive = true
seeNewPostsButton.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
seeNewPostsButton.widthAnchor.constraint(equalToConstant: seeNewPostsButton.button.bounds.width).isActive = true
seeNewPostsButton.button.addTarget(self, action: #selector(handleRefresh), for: .touchUpInside)
//observePosts()
beginBatchFetch()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
listenForNewPosts()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopListeningForNewPosts()
}
func toggleSeeNewPostsButton(hidden:Bool) {
if hidden {
// hide it
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {
self.seeNewPostsButtonTopAnchor.constant = -44.0
self.view.layoutIfNeeded()
}, completion: nil)
} else {
// show it
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {
self.seeNewPostsButtonTopAnchor.constant = 12
self.view.layoutIfNeeded()
}, completion: nil)
}
}
#objc func handleRefresh() {
print("Refresh!")
toggleSeeNewPostsButton(hidden: true)
newPostsQuery.queryLimited(toFirst: 20).observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [Post]()
let firstPost = self.posts.first
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, data),
childSnapshot.key != firstPost?.id {
tempPosts.insert(post, at: 0)
}
}
self.posts.insert(contentsOf: tempPosts, at: 0)
let newIndexPaths = (0..<tempPosts.count).map { i in
return IndexPath(row: i, section: 0)
}
self.refreshControl.endRefreshing()
self.tableView.insertRows(at: newIndexPaths, with: .top)
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
self.listenForNewPosts()
})
}
func fetchPosts(completion:#escaping (_ posts:[Post])->()) {
oldPostsQuery.queryLimited(toLast: 20).observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [Post]()
let lastPost = self.posts.last
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, data),
childSnapshot.key != lastPost?.id {
tempPosts.insert(post, at: 0)
}
}
return completion(tempPosts)
})
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height * leadingScreensForBatching {
if !fetchingMore && !endReached {
beginBatchFetch()
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 72.0
}
func beginBatchFetch() {
fetchingMore = true
self.tableView.reloadSections(IndexSet(integer: 1), with: .fade)
fetchPosts { newPosts in
self.posts.append(contentsOf: newPosts)
self.fetchingMore = false
self.endReached = newPosts.count == 0
UIView.performWithoutAnimation {
self.tableView.reloadData()
self.listenForNewPosts()
}
}
}
var postListenerHandle:UInt?
func listenForNewPosts() {
guard !fetchingMore else { return }
// Avoiding duplicate listeners
stopListeningForNewPosts()
postListenerHandle = newPostsQuery.observe(.childAdded, with: { snapshot in
if snapshot.key != self.posts.first?.id,
let data = snapshot.value as? [String:Any],
let post = Post.parse(snapshot.key, data) {
self.stopListeningForNewPosts()
if snapshot.key == self.lastUploadedPostID {
self.handleRefresh()
self.lastUploadedPostID = nil
} else {
self.toggleSeeNewPostsButton(hidden: false)
}
}
})
}
func stopListeningForNewPosts() {
if let handle = postListenerHandle {
newPostsQuery.removeObserver(withHandle: handle)
postListenerHandle = nil
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let newPostNavBar = segue.destination as? UINavigationController,
let newPostVC = newPostNavBar.viewControllers[0] as? NewPostViewController {
newPostVC.delegate = self
}
}
}
extension HomeController: NewPostVCDelegate {
func didUploadPost(withID id: String) {
self.lastUploadedPostID = id
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
I try to make a like button and unlike but I it gives me error when I try to press the button in the simulator .if you know any other code for the like button to be much easier will be helpful ( or some websites , yt vids)
On this line
ref.child("posts").child(self.postID)
the postID is undefined which is causing the crash. In the comments you state you assign a value to it, but the code in the question doesn't include how that's being done so there's a high likelyhood that value is not being assigned.
The fix would be to assign a value to that class var before that line, like this
#IBAction func likePressed(_ sender: Any) {
self.postID = "post_0" //or however you determine which post it is
// e.g. self.postID = getCurrentPostId()
let ref = Database.database().reference()
let keyToPost = ref.child("posts").childByAutoId().key
ref.child("posts").child(self.postID)...
You may also want to implement some basic error checking as well to ensure the postID is not nil before trying to call the Firebase function.
if let postID = getCurrentPostID() {
//perform the firebase function using postID
} else {
//display an error 'not post selected'
}
I'm changing my app to start to use toolbar buttons for the main pages instead of a menu. All of the menu links work and the toolbar buttons work on every page except one. When I click on the button the app freezes, and Xcode brings me to this line:
'weak var itemDetailPage = segue.destination as? ItemDetailViewController' with this error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
All the variables are nil.
Any help would be monumental!
Here is the code for the view controller it is specifically happening on:
import UIKit
import AVFoundation
import Alamofire
#objc protocol FavRefreshLikeCountsDelegate: class {
func updateLikeCounts(_ likeCount: Int)
}
#objc protocol FavRefreshReviewCountsDelegate: class {
func updateReviewCounts(_ reviewCount: Int)
}
class FavouriteItemsViewController: UICollectionViewController {
var populationItems = false
var selectedSubCategoryId:Int = 0
var selectedCityId:Int = 1
var selectedCityLat: String!
var selectedCityLng: String!
var items = [ItemModel]()
var currentPage = 0
var loginUserId:Int = 0
weak var selectedCell : AnnotatedPhotoCell!
#IBOutlet weak var menuButton: UIBarButtonItem!
var defaultValue: CGPoint!
override func viewDidLoad() {
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
collectionView!.register(AnnotatedPhotoCell.classForCoder(), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "AnnotatedPhotoCell")
if let layout = collectionView?.collectionViewLayout as? PinterestLayout {
layout.delegate = self
}
loadLoginUserId()
loadFavouriteItems()
defaultValue = collectionView?.frame.origin
animateCollectionView()
}
override func viewDidAppear(_ animated: Bool) {
updateNavigationStuff()
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return UIStatusBarStyle.lightContent
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
loadFavouriteItems()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
weak var itemCell = sender as? AnnotatedPhotoCell
weak var itemDetailPage = segue.destination as? ItemDetailViewController
itemDetailPage!.selectedItemId = Int((itemCell!.item?.itemId)!)!
itemDetailPage!.selectedShopId = Int((itemCell!.item?.itemShopId)!)!
itemDetailPage!.favRefreshLikeCountsDelegate = self
itemDetailPage!.favRefreshReviewCountsDelegate = self
selectedCell = itemCell
updateBackButton()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell : AnnotatedPhotoCell
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnnotatedPhotoCell", for: indexPath) as! AnnotatedPhotoCell
cell.item = items[(indexPath as NSIndexPath).item]
let imageURL = configs.imageUrl + items[(indexPath as NSIndexPath).item].itemImage
cell.imageView?.loadImage(urlString: imageURL) { (status, url, image, msg) in
if(status == STATUS.success) {
print(url + " is loaded successfully.")
self.items[indexPath.item].itemImageBlob = image
}else {
print("Error in loading image" + msg)
}
}
cell.imageView.alpha = 0
cell.captionLabel.alpha = 0
UIView.animate(withDuration: 1, delay: 0, options: UIViewAnimationOptions.curveEaseIn, animations: {
cell.imageView.alpha = 1.0
cell.captionLabel.alpha = 1.0
}, completion: nil)
return cell
}
func updateBackButton() {
let backItem = UIBarButtonItem()
backItem.title = ""
navigationItem.backBarButtonItem = backItem
}
func updateNavigationStuff() {
self.navigationController?.navigationBar.topItem?.title = language.favouritePageTitle
self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedStringKey.font: UIFont(name: customFont.boldFontName, size: CGFloat(customFont.boldFontSize))!, NSAttributedStringKey.foregroundColor:UIColor.white]
self.navigationController!.navigationBar.barTintColor = Common.instance.colorWithHexString(configs.barColorCode)
}
func loadLoginUserId() {
let plistPath = Common.instance.getLoginUserInfoPlist()
let myDict = NSDictionary(contentsOfFile: plistPath)
if let dict = myDict {
loginUserId = Int(dict.object(forKey: "_login_user_id") as! String)!
print("Login User Id : " + String(loginUserId))
} else {
print("WARNING: Couldn't create dictionary from LoginUserInfo.plist! Default values will be used!")
}
}
func loadFavouriteItems() {
if self.currentPage == 0 {
_ = EZLoadingActivity.show("Loading...", disableUI: true)
}
Alamofire.request(APIRouters.GetFavouriteItems( loginUserId, configs.pageSize, self.currentPage)).responseCollection {
(response: DataResponse<[Item]>) in
if self.currentPage == 0 {
_ = EZLoadingActivity.hide()
}
if response.result.isSuccess {
if let items: [Item] = response.result.value {
if(items.count > 0) {
for item in items {
let oneItem = ItemModel(item: item)
self.items.append(oneItem)
self.currentPage+=1
}
}
}
self.collectionView!.reloadData()
} else {
print(response)
}
}
}
func animateCollectionView() {
moveOffScreen()
UIView.animate(withDuration: 1, delay: 0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.collectionView?.frame.origin = self.defaultValue
}, completion: nil)
}
fileprivate func moveOffScreen() {
collectionView?.frame.origin = CGPoint(x: (collectionView?.frame.origin.x)!,
y: (collectionView?.frame.origin.y)! + UIScreen.main.bounds.size.height)
}
}
extension FavouriteItemsViewController : PinterestLayoutDelegate {
func collectionView(_ collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath , withWidth width:CGFloat) -> CGFloat {
let item = items[(indexPath as NSIndexPath).item]
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let size = CGSize(width: item.itemImageWidth, height: item.itemImageHeight)
var rect : CGRect
if item.itemImageBlob != nil {
rect = AVMakeRect(aspectRatio: item.itemImageBlob!.size, insideRect: boundingRect)
}else{
rect = AVMakeRect(aspectRatio: size, insideRect: boundingRect)
}
return rect.size.height
}
func collectionView(_ collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat {
let annotationPadding = CGFloat(4)
let annotationHeaderHeight = CGFloat(17)
let height = annotationPadding + annotationHeaderHeight + annotationPadding + 30
return height
}
}
extension FavouriteItemsViewController : FavRefreshLikeCountsDelegate, FavRefreshReviewCountsDelegate {
func updateLikeCounts(_ likeCount: Int) {
if selectedCell != nil {
selectedCell.lblLikeCount.text = "\(likeCount)"
}
}
func updateReviewCounts(_ reviewCount: Int) {
if selectedCell != nil {
selectedCell.lblReviewCount.text = "\(reviewCount)"
}
}
}
You should try be checking your ItemDetailViewController.
for example:
if segue.identifier == "ItemDetailVC" {
weak var itemCell = sender as? AnnotatedPhotoCell
weak var itemDetailPage = segue.destination as? ItemDetailViewController
...
}
Overview: How can I make it so that when the cell with the segue
identifier of 0 will load data in the row that has 0 as its ID, cell with segue identifier of 1 will load data in the row that has 1 as its ID, and so on and so forth. The data includes ID (responds to cell identifier), navTitle (title of the navigation bar), written (who the article was written by), date (date of the article), and article (the article itself).
I am trying to make it so that once a cell is tapped in my tableview it opens data unique to that cell. What would the best way to do that? I was thinking that maybe I should have it check the ID column I have on Parse and load data the data in that row, but I'm not sure how to do that. Is there a better way to do this? Any help is appreciated! Feel free to ask me for any additional information.
^ So here I have a tableview in which data is being taken from Parse and used for both labels.
^ This view controller is segued to the cell above with:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
self.performSegueWithIdentifier("0a", sender: nil)
self.tableview.deselectRowAtIndexPath(indexPath, animated: true)
}
}
Here is my EventsDetailViewController, the one that is called when a cell is tapped:
import UIKit
import Parse
import ParseUI
import Bolts
class EventDetailViewController: UIViewController {
#IBAction func eventDetail(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBOutlet weak var articleTitle: UILabel!
#IBOutlet weak var writtenBy: UILabel!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var article: UITextView!
#IBOutlet weak var navBar: UINavigationBar!
var dateDetail = [String]()
var articleDetail = [String]()
override func viewDidLoad() {
super.viewDidLoad()
loadEvents()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadEvents () -> Void {
var query = PFQuery(className: "eventsdetail")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error : NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
var output1 = object.objectForKey("navTitle") as! String
self.navBar.topItem?.title = output1
var output2 = object.objectForKey("articleTitle") as! String
self.articleTitle.text = output2
var output3 = object.objectForKey("written") as! String
self.writtenBy.text = output3
var output4 = object.objectForKey("date") as! String
self.date.text = output4
var output5 = object.objectForKey("article") as! String
self.article.text = output5
}
}
}
}
}
}
Here is my EventsViewController:
import UIKit
import Parse
import Bolts
import ParseUI
class EventsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var timer: NSTimer!
var isAnimating = false
var currentColorIndex = 0
var currentLabelIndex = 0
var customView: UIView!
var labelsArray: Array<UILabel> = []
var refreshControl: UIRefreshControl!
var testArray = [String]()
var subArray = [String]()
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
tableview.delegate = self
tableview.dataSource = self
refreshControl.addTarget(self, action: Selector("loadEvents"), forControlEvents: UIControlEvents.ValueChanged)
self.tableview.addSubview(refreshControl)
loadEvents()
loadCustomRefreshContents()
//refreshControl colors
refreshControl.backgroundColor = UIColor.clearColor() //color of background
refreshControl.tintColor = UIColor.clearColor() //color of indicator
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadEvents () -> Void {
testArray = [String]()
subArray = [String]()
let query = PFQuery(className: "events")
let runkey = query.orderByDescending("eventTitle")
runkey.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error:NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
let load = object.objectForKey("eventTitle") as! String
self.testArray.append(load)
let subload = object.objectForKey("date") as! String
self.subArray.append(subload)
//reload TableView
self.tableview.reloadData()
print(self.testArray)
}
}
} else {
print("error:\(error!) \(error!.userInfo)")
}
}
refreshControl.endRefreshing()
}
func do_table_refresh() {
dispatch_async(dispatch_get_main_queue()) {
self.tableview.reloadData()
return
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
self.performSegueWithIdentifier("0a", sender: nil)
self.tableview.deselectRowAtIndexPath(indexPath, animated: true)
}
}
func tableView(tableView:UITableView!, numberOfRowsInSection section:Int) -> Int {
return testArray.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("eventcell", forIndexPath: indexPath) as! EventTableViewCell
cell.title.text = self.testArray[indexPath.row]
cell.subTitle.text = self.subArray[indexPath.row]
return cell
}
//refreshes tableview; starts refresh
func loadCustomRefreshContents() {
let refreshContents = NSBundle.mainBundle().loadNibNamed("RefreshControl", owner: self, options: nil)
customView = refreshContents[0] as! UIView
customView.frame = refreshControl.bounds
for var i=0; i<customView.subviews.count; ++i {
labelsArray.append(customView.viewWithTag(i + 1) as! UILabel)
}
refreshControl.addSubview(customView)
}
//stops refresh
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
do_table_refresh()
if refreshControl.refreshing {
if !isAnimating {
animateRefreshStep1()
}
}
}
//cycles through colors
func getNextColor() -> UIColor {
var colorsArray: Array<UIColor> = [UIColor.magentaColor(), UIColor.brownColor(), UIColor.yellowColor(), UIColor.redColor(), UIColor.greenColor(), UIColor.blueColor(), UIColor.orangeColor()]
if currentColorIndex == colorsArray.count {
currentColorIndex = 0
}
let returnColor = colorsArray[currentColorIndex]
++currentColorIndex
return returnColor
}
func doSomething() {
timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: "endOfWork", userInfo: nil, repeats: true)
self.do_table_refresh()
}
func endOfWork() {
refreshControl.endRefreshing()
timer.invalidate()
timer = nil
}
//first part of animation
func animateRefreshStep1() {
isAnimating = true
UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
self.labelsArray[self.currentLabelIndex].textColor = self.getNextColor()
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformIdentity
self.labelsArray[self.currentLabelIndex].textColor = UIColor.blackColor()
}, completion: { (finished) -> Void in
++self.currentLabelIndex
if self.currentLabelIndex < self.labelsArray.count {
self.animateRefreshStep1()
}
else {
self.animateRefreshStep2()
}
})
})
}
//second part of animation
func animateRefreshStep2() {
UIView.animateWithDuration(0.35, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[0].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[1].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[2].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[3].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[4].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[5].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[6].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[7].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[8].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[9].transform = CGAffineTransformMakeScale(1.5, 1.5)
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[0].transform = CGAffineTransformIdentity
self.labelsArray[1].transform = CGAffineTransformIdentity
self.labelsArray[2].transform = CGAffineTransformIdentity
self.labelsArray[3].transform = CGAffineTransformIdentity
self.labelsArray[4].transform = CGAffineTransformIdentity
self.labelsArray[5].transform = CGAffineTransformIdentity
self.labelsArray[6].transform = CGAffineTransformIdentity
self.labelsArray[7].transform = CGAffineTransformIdentity
self.labelsArray[8].transform = CGAffineTransformIdentity
self.labelsArray[9].transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
if self.refreshControl.refreshing {
self.currentLabelIndex = 0
self.animateRefreshStep1()
}
else {
self.isAnimating = false
self.currentLabelIndex = 0
for var i=0; i<self.labelsArray.count; ++i {
self.labelsArray[i].textColor = UIColor.blackColor()
self.labelsArray[i].transform = CGAffineTransformIdentity
}
}
})
})
}
}
Since you have the data on the tableView just make sure you create a segue in your storyboard connecting the prototype cell and the destination view controller and add an identifier to the segue.
Next, on the controller with the tableView make sure you call:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "segueIdentifier"){
let detailScene = segue.destinationViewController as! EventDetailViewController
if let indexPath = self.tableView.indexPathForSelectedRow {
let row = Int(indexPath.row)
//here you can pass all the data to the destination viewController. But you CANT POPULATE YOUR LABELS. make sure you pass the data to some auxiliar variable(s). Next line is an example
detailScene.selectedData = (self.myData[row] as! PFObject)
}
}
}
Update:
detailScene.selectedData = (self.myData[row] as! PFObject)
detailScene is an instance of the destination viewController. selectedData is, in this case an instance of PFObject that will be use to populate the labels in my destination viewController. self.myData is an array in my source viewController with the data that is shown in my tableView.
So if you want to send the data you show in your cells. (
self.testArray[indexPath.row]
self.subArray[indexPath.row]
) make sure you declare the variables that will receive this data on your EventsDetailViewController something like this:
var testString:String!
var subString:String!
Then in your prepareForSegue (EventsViewController) make sure to instantiate this variables with the proper data:
detailScene.test = self.testArray[row]
detailScene.sub = self.subArray[row]
Finally on EventsDetailViewController you can pass the data to the labels you want on viewDidLoad():
self.articleTitle.text = testString
Let me know in case of any concerns.
What you are asking is the archetypical basic app: show a list of items in a tableview and being able to tap on a row to see more details.
What seems unusual to me is how you have two different classes: events and eventdetails.
Compare it with a database of books. You would not have a Book class and a BookDetails class. You would only have a Book class that contains all details about a book. So when you want to show a list of books, you fetch all the Book objects. In the tableviewcontroller you display a subset of fields from the Book objects (like i.e. title and author). When the user then taps on a specific book, you open a viewcontroller and pass it the Book object for that row. This detail viewcontroller then just shows MORE info/fields from that same Book object.
I am working on making a social media app clone using tutorials for practice in Swift, but I want to add a like button with Parse but not sure how. Does anyone know a tutorial or do you know how you would go about doing this? I attempted to make one like this but I'm pretty sure it is not correct...
I put this in the cell view controller
#IBAction func likeButton(sender: AnyObject) {
a++
var post = PFObject(className: "Post")
post["likes"] = a
}
}
This went into the feed view controller...
var messages = [String]()
var usernames = [String]()
var imageFiles = [PFFile]()
var users = [String: String]()
var likesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var query = PFUser.query()
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let users = objects {
self.messages.removeAll(keepCapacity: true)
self.users.removeAll(keepCapacity: true)
self.imageFiles.removeAll(keepCapacity: true)
self.usernames.removeAll(keepCapacity: true)
self.likesArray.removeAll(keepCapacity: true)
for object in users {
if let user = object as? PFUser {
self.users[user.objectId!] = user.username!
}
}
}
})
var getFollowedUsersQuery = PFQuery(className: "followers")
getFollowedUsersQuery.whereKey("follower", equalTo: PFUser.currentUser()!.objectId!)
getFollowedUsersQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
var followedUser = object["following"] as! String
var query = PFQuery(className: "Post")
query.whereKey("userId", equalTo: followedUser)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.messages.append(object["message"] as! String)
self.imageFiles.append(object["imageFile"] as! PFFile)
self.usernames.append(self.users[object["userId"] as! String]!)
self.likesArray.append(object["likes"] as? String)
self.tableView.reloadData()
}
}
})
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
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 usernames.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! cell
imageFiles[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
myCell.postedImage.image = downloadedImage
}
}
myCell.username.text = usernames[indexPath.row]
myCell.message.text = messages[indexPath.row]
myCell.likeLabel.text = likesArray[indexPath.row]
myCell.selectionStyle = .None
return myCell
}
}
This is the view controller when you post the picture and message...
var likes = 0
var picSelected = false
#IBOutlet var feed: UIButton!
func displayAlert(title: String, message: String) {
var alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
}))
presentViewController(alert, animated: true, completion: nil)
}
var activityIndicator = UIActivityIndicatorView()
#IBOutlet var imageToPost: UIImageView!
#IBAction func chooseImage(sender: AnyObject) {
var image = UIImagePickerController()
image.delegate = self
image.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
image.allowsEditing = false
presentViewController(image, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
dismissViewControllerAnimated(true, completion: nil)
imageToPost.image = image
picSelected = true
}
#IBOutlet var message: UITextField!
#IBAction func postImage(sender: AnyObject) {
if message.text != "" && picSelected != false {
picSelected = false
activityIndicator = UIActivityIndicatorView(frame: view.frame)
activityIndicator.backgroundColor = UIColor(white: 1.0, alpha: 0.5)
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
var post = PFObject(className: "Post")
post["message"] = message.text
post["userId"] = PFUser.currentUser()?.objectId!
let imageData = UIImagePNGRepresentation(imageToPost.image)
let imageFile = PFFile(name: "image.png", data: imageData)
post["imageFile"] = imageFile
post["likes"] = a
post.saveInBackgroundWithBlock { (success, error) -> Void in
self.activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
if error == nil {
self.displayAlert("Image Posted!", message: "Your image has been posted successfully.")
self.imageToPost.image = UIImage(named: "Blank-Person1.png")
self.message.text = ""
}
}
} else {
displayAlert("Could Not Post Image!", message: "Please enter a message and/or select a picture.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I'm about to make Like button like this:
Create Names column in parse ( Array column) to hold names who click on like button
Create Likes column in parse (integer column) to hold count number of Names column
and when someone click on like button add his name to Names Array in parse, If he click it again remove his name
And you need to take all names in NamesArray column and count them in swift then update likes column
i'm not sure but that's in my mind right now, but if you think of it you'll get better way.