I'm getting record from server using php
As i'm getting 11 record currently so i want in starting i will show just 6 records and remaining next 5 record will show when user reached at last cell while scrolling. So this process is in working form, but the problem is while running, it working so fast that before reaching last row all records are already showing while scrolling and the activity indicator is just animating at the bottom of tableView.
I don't know what is the problem.
Also i want when user reached at last cell, activity indicator start animating while loading data .
Here is my code
import UIKit
import AlamofireImage
struct property{
let property_Id : String
let propertyTitle : String
let imageURL : String
let user_Id : String
let area : String
let bed : String
let unit : String
let bath : String
let price : String
}
class searchRecordsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,favouriteButtonTableViewCellDelegate {
var y = 6
var m = 12
#IBOutlet weak var tableView : UITableView!
var RESULT : [NSDictionary] = []
var myProperty = [property]()
var myPropertyCopy = [property]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: Getting dictionary data from previous controller
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = "Results"
self.navigationController?.navigationBar.tintColor = UIColor.black
//MARK: Getting dictionary data from previous controller
for item in RESULT {
let propertyInfo = property(property_Id: String(item["propertyId"]! as! Int), propertyTitle: item["propertyTitle"]! as! String, imageURL: item["imagePath"]! as! String, user_Id: String(item["userId"]! as! Int), area: item["area"]! as! String, bed: item["bed"]! as! String, unit: item["unit"]! as! String, bath: item["bath"]! as! String, price: item["price"]! as! String )
myProperty.append(propertyInfo)
}
//MARK: Inserting first 6 records in Array
for i in 0 ..< 6 {
if !(myProperty.indices.contains(i)) {
break
}
myPropertyCopy.append(myProperty[i])
}
}
func downloadImage(imagePath : String, theIMAGEVIEW : UIImageView) {
let myUrl = URL(string: URL_IP+imagePath);
//MARK: AlamofireImage to download the image
theIMAGEVIEW.af_setImage(withURL: myUrl!, placeholderImage: #imageLiteral(resourceName: "addProperty"), filter: nil, progress: nil, runImageTransitionIfCached: true, completion: nil)
}
func tableView(_ tableView:UITableView, numberOfRowsInSection section:Int) -> Int
{
return myPropertyCopy.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "RecordCell") as! searchrecordsTableViewCell
let property = myPropertyCopy[indexPath.row]
cell.area.text = property.area+" "+property.unit
if property.bath == ""{
cell.bath.text = property.bath
}
else{
cell.bath.text = property.bath+" Baths"
}
if property.bed == ""{
cell.bed.text = property.bed
}
else{
cell.bed.text = property.bed+" Baths"
}
cell.propertyTitle.text = property.propertyTitle
cell.price.text = convertAMOUNT(price : property.price)
downloadImage(imagePath: property.imageURL, theIMAGEVIEW: cell.myImageView)
//----
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if myPropertyCopy.count != myProperty.count{
let lastRow = myPropertyCopy.count - 1
if indexPath.row == lastRow {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
spinner.startAnimating()
spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
self.tableView.tableFooterView = spinner
self.tableView.tableFooterView?.isHidden = false
moreData()
}
}
}
func moreData(){
for i in y ..< m {
if !(myProperty.indices.contains(i)) {
break
}
myPropertyCopy.append(myProperty[i])
}
y = y + 10
m = m + 10
self.tableView.reloadData()
}
}
currently my TableView looks like
See output here
Thanks in Advance.
After a long practice i have done my problem, i have just added UIScrollView delegate function for checking if the user reached at last cell then start activityIndicator for 3 seconds and then load data and that's it.
As i have just very small amount of data that's why i'm start ing UIactivityIndicator for 3 seconds before getting data.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// UITableView only moves in one direction, y axis
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
if maximumOffset - currentOffset <= 10.0 {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
if myPropertyCopy.count != myProperty.count {
//print("this is the last cell")
spinner.startAnimating()
spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
spinner.hidesWhenStopped = true
self.tableView.tableFooterView = spinner
self.tableView.tableFooterView?.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
//MARK: Loading more data
self.moreData()
}
}
else{
self.tableView.tableFooterView?.isHidden = true
spinner.stopAnimating()
}
}
}
Related
I am trying to display IPFS image in swift using UIImageView
. When I run my code in xCode/Simulator the image shows fine.
. When I run my code in Xcode/My iPhone device connected via USB works. I do see the image
But when run the app on my phone (after app is installed via Xcode) the IPFS image is not shows for the UIImageView - what am I doing wrong?
func tableView(_ myTableViewAccount: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TableViewCellAccount = myTableViewAccount.dequeueReusableCell(withIdentifier: "aCell") as! TableViewCellAccount
guard (self.accountsArray.count > 0) else { return cell }
print("accountArray=\(self.accountsArray) indexPath=\(indexPath.row)")
let count = self.accountsArray.count
if (indexPath.row < count) {
guard (self.accountsArray[indexPath.row].name != nil) else { return cell }
cell.accountImgView?.frame.size = CGSize(width: 50, height: 50)
cell.accountImgView.center = view.center
cell.accountImgView.layer.cornerRadius = 18
cell.accountImgView?.clipsToBounds = true
let activityIndicator = UIActivityIndicatorView()
activityIndicator.frame = cell.accountImgView.bounds
cell.accountImgView.addSubview(activityIndicator)
activityIndicator.backgroundColor = UIColor.white
activityIndicator.startAnimating()
let front_img_url = "https://cloudflare-ipfs.com/ipfs/QmYFDgVBMrRfEm5JpVSWeSDAfTUpboEiL8rZyGym24MNVu"
let frontImageURL = URL(string: front_img_url)
if frontImageURL != nil {
DispatchQueue.main.async {
let dataProdFrontImg = try? Data(contentsOf: frontImageURL!)
if let data_front_img = dataProdFrontImg { //fromn img
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
let accountImage = UIImage(data: data_front_img)
cell.accountImgView.image = accountImage
}
}
}
}
return cell
}
... Cell Table is defined like this
class TableViewCellAccount: UITableViewCell {
#IBOutlet weak var accountImgView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I moved away from UIImage View instead, I used
#IBOutlet weak var accountImgView: WKWebView?
that fixed my issue
I recently started using CVCalendar to create a CalendarView with a Week presentation.
I created a UILabel called monthLabel which has an outlet to the view's controller
The ViewController for this particular view is this one:
import UIKit
import CVCalendar
class EventsViewController: UIViewController {
#IBOutlet weak var headerView: UIView!
#IBOutlet weak var menuView: CVCalendarMenuView!
#IBOutlet weak var calendarView: CVCalendarView!
#IBOutlet weak var eventsTable: UITableView!
#IBOutlet weak var monthLabel: UILabel!
private var currentCalendar: Calendar?
private var animationFinished: Bool = true
let mainAppViewHeader: MainAppViewHeader = UIView.fromNib()
override func viewDidLoad() {
super.viewDidLoad()
loadViewHeader()
setupTable()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupDatePicker()
}
override func awakeFromNib() {
let timeZoneBias = 480 // (UTC+08:00)
currentCalendar = Calendar(identifier: .gregorian)
currentCalendar?.locale = Locale(identifier: "fr_FR")
if let timeZone = TimeZone(secondsFromGMT: -timeZoneBias * 60) {
currentCalendar?.timeZone = timeZone
}
}
func setupDatePicker() {
menuView.delegate = self
calendarView.delegate = self
menuView.backgroundColor = ColorManager.Gray5
calendarView.backgroundColor = ColorManager.Gray5
calendarView.appearance.dayLabelPresentWeekdaySelectedBackgroundColor = ColorManager.PrimaryColor
calendarView.appearance.dayLabelWeekdaySelectedBackgroundColor = ColorManager.PrimaryColor
if let currentCalendar = currentCalendar {
monthLabel.text = CVDate(date: Date(), calendar: currentCalendar).globalDescription
}
menuView.commitMenuViewUpdate()
calendarView.commitCalendarViewUpdate()
}
func loadViewHeader() {
mainAppViewHeader.setupHeader()
headerView.addSubview(mainAppViewHeader)
}
func setupTable() {
let rowNib = UINib(nibName: "EventTableCell", bundle: nil)
eventsTable.dataSource = self
eventsTable.delegate = self
eventsTable.register(rowNib, forCellReuseIdentifier: "eventCell")
eventsTable.separatorStyle = .none
}
}
extension EventsViewController: CVCalendarViewDelegate, CVCalendarMenuViewDelegate {
func presentationMode() -> CalendarMode {
return .weekView
}
func firstWeekday() -> Weekday {
return .sunday
}
func shouldAutoSelectDayOnWeekChange() -> Bool {
return true
}
func disableScrollingBeforeDate() -> Date {
let currentDate = Date()
return currentDate.addingTimeInterval(-8 * 24 * 60 * 60)
}
func disableScrollingBeyondDate() -> Date {
let currentDate = Date()
return currentDate.addingTimeInterval(180 * 24 * 60 * 60)
}
func maxSelectableRange() -> Int {
return 1
}
func shouldSelectRange() -> Bool {
false
}
func earliestSelectableDate() -> Date {
let currentDate = Date()
return currentDate.addingTimeInterval(-8 * 24 * 60 * 60)
}
func latestSelectableDate() -> Date {
let currentDate = Date()
return currentDate.addingTimeInterval(180 * 24 * 60 * 60)
}
func shouldShowWeekdaysOut() -> Bool {
return true
}
func didShowPreviousWeekView(from startDayView: DayView, to endDayView: DayView) {
print(startDayView, endDayView)
}
func presentedDateUpdated(_ date: CVDate) {
monthLabel.text = "asdf" //calendarView.presentedDate.globalDescription
print(date.globalDescription)
}
}
extension EventsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let eventCell = tableView.dequeueReusableCell(withIdentifier: "eventCell") as? EventTableCell else {
return UITableViewCell()
}
eventCell.setupCell()
eventCell.updateConstraintsIfNeeded()
return eventCell
}
}
extension EventsViewController: UITableViewDelegate {
}
And the outlet reference:
The issue here is that even tho I'm telling the UILabel to change its text whenever the presented date is updated:
func presentedDateUpdated(_ date: CVDate) {
monthLabel.text = "asdf" //calendarView.presentedDate.globalDescription
print(date.globalDescription)
}
It's not updating its value, yet I get the date.globalDescription value printed in the console.
Is there something I'm missing when trying to update the label?
I tried using the code in the official example:
func presentedDateUpdated(_ date: CVDate) {
if monthLabel.text != date.globalDescription && self.animationFinished {
let updatedMonthLabel = UILabel()
updatedMonthLabel.textColor = monthLabel.textColor
updatedMonthLabel.font = monthLabel.font
updatedMonthLabel.textAlignment = .center
updatedMonthLabel.text = date.globalDescription
updatedMonthLabel.sizeToFit()
updatedMonthLabel.alpha = 0
updatedMonthLabel.center = self.monthLabel.center
let offset = CGFloat(48)
updatedMonthLabel.transform = CGAffineTransform(translationX: 0, y: offset)
updatedMonthLabel.transform = CGAffineTransform(scaleX: 1, y: 0.1)
UIView.animate(withDuration: 0.35, delay: 0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.animationFinished = false
self.monthLabel.transform = CGAffineTransform(translationX: 0, y: -offset)
self.monthLabel.transform = CGAffineTransform(scaleX: 1, y: 0.1)
self.monthLabel.alpha = 0
updatedMonthLabel.alpha = 1
updatedMonthLabel.transform = CGAffineTransform.identity
}) { _ in
self.animationFinished = true
self.monthLabel.frame = updatedMonthLabel.frame
self.monthLabel.text = updatedMonthLabel.text
self.monthLabel.transform = CGAffineTransform.identity
self.monthLabel.alpha = 1
updatedMonthLabel.removeFromSuperview()
}
self.view.insertSubview(updatedMonthLabel, aboveSubview: self.monthLabel)
}
}
But this approach is only pushing a new view with the updated month and then dismissing the view, and while it contains basically the same code that I was using:
self.monthLabel.text = updatedMonthLabel.text
The monthLabel is not being updated, this is what happens when using the above code, it reverts back to original text.
If I remove this line:
updatedMonthLabel.removeFromSuperview()
Both texts get overlapped, so it's ignoring this line:
self.monthLabel.text = updatedMonthLabel.text
I've done some extra testing, and checked that the label is correctly linked with an outlet to my view, I changed the text color:
func presentedDateUpdated(_ date: CVDate) {
print(Thread.current)
print("Wololo")
monthLabel.textColor = UIColor.green
monthLabel.text = "asdf"
// calendarView.contentController.refreshPresentedMonth()
// monthLabel.text = "asdf" //calendarView.presentedDate.globalDescription
// print(date.globalDescription)
}
It prints:
<NSThread: 0x600002b6c100>{number = 1, name = main}
Wololo
N number of times I change the day by clicking on the calendar, however the text remains unchanged:
For some reason the code inside:
override func awakeFromNib() {
let timeZoneBias = 480 // (UTC+08:00)
currentCalendar = Calendar(identifier: .gregorian)
currentCalendar?.locale = Locale(identifier: "fr_FR")
if let timeZone = TimeZone(secondsFromGMT: -timeZoneBias * 60) {
currentCalendar?.timeZone = timeZone
}
}
was in conflict with not updating the label, if we remove the above code and instead do this:
func setupDatePicker() {
menuView.delegate = self
calendarView.delegate = self
menuView.commitMenuViewUpdate()
calendarView.commitCalendarViewUpdate()
monthLabel.text = calendarView.presentedDate.globalDescription //This line at the bottom of this method
}
Everything works once again.
If someone knows the reason behind it, feel free to make a comment and I'll add it to the answer.
presentedDateUpdated is likely being called on a background thread making it so your UI changes aren't being displayed. you can confirm this by printing Thread.current inside presentedDateUpdated.
a simple solution would be this:
func presentedDateUpdated(_ date: CVDate) {
DispatchQueue.main.async { [weak self] in
self?.monthLabel.text = "asdf" //calendarView.presentedDate.globalDescription
print(date.globalDescription)
}
}
I have a following setup:
UploadedCell
DisplayUploadsVC
UploadHelper with delegate that tracks
progress (this is singleton)
In my controller I have a timer in cellForItemAt that gets the progress for uploadId that is currently uploading and update the cell upload item.
In my cell I use prepareForReuse and set my upload to nil.
But again when I scroll and cells reuse I see duplicate cells. I also see when I pullToRefress or go to the end of results to get more results from server.
Just not sure what I'm missing or if I can use this kind of implementation with timer in collection view cell to get the progress.
UploadedCell
class UploadedCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// MARK: Variables declaration
let uploadBadge = UIImageView(image: #imageLiteral(resourceName: "uploads") , contentMode: .scaleAspectFit)
let uploadName = UILabel(font: UIFont.openSansSemiboldFontOfSize(18))
let statusName = UILabel(font: UIFont.openSansSemiboldFontOfSize(18))
#objc lazy var moreButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "dots-menu")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = .lightGray
button.addTarget(self, action: #selector(handleMore), for: .touchDown)
return button
}()
// MARK - didSet
var upload: UploadModel.UploadItem? {
didSet {
uploadName.text = upload?.title
statusName.text = upload?.status
}
}
// MARK: - Main Init
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
setupViews()
}
override func prepareForReuse() {
self.upload = nil
super.prepareForReuse()
}
}
DisplayUploadsVC
class DisplayUploadsVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// MARK: - Properties
/// various object init
var uploadCell: UploadCell = UploadCell()
var uploadProgress = (progress: Float(0), uploadId: "")
var progressTimer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
UploadHelper.shared.uploadHelperDelegate = self
setupViews()
setupEmptyBackgroundView()
fetchUploads()
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! UploadCell
cell.upload = nil
var upload = isSearching ? filteredResults[indexPath.item] : results?[indexPath.item]
cell.upload = upload
// get uploads with paging if we are paginating
if (self.uploadProgress.uploadId == upload?.id && self.uploadProgress.progress < 99) {
progressTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
upload?.status = String(format: NSLocalizedString("Uploading %.0f%%", comment: ""), self.uploadProgress.progress)
cell.upload = upload
if self.uploadProgress.progress > 99.9 {
self.progressTimer.invalidate()
upload?.status = NSLocalizedString("Upload DONE", comment: "")
cell.upload = upload
}
}
}
return cell
}
extension DisplayUploadsVC : UploadHelperProgress {
func showProgress(progress: Float, uploadId: String) {
self.uploadProgress.progress = progress
self.uploadProgress.uploadId = uploadId
}
}
UploadHelper
protocol UploadHelperProgress : class {
func showProgress(progress: Float, uploadId: String)
}
private var id: String?
private let progressBlock = { bytesWritten, bytesTotal in
var progress = Float(bytesWritten) / Float(bytesTotal)
UploadHelper.shared.uploadHelperDelegate?.showProgress(progress: progress * 100, uploadId: id ?? "")
} as UploadProgressBlock
class UploadHelper: NSObject {
/// delegate
weak var uploadHelperDelegate: UploadHelperProgress?
/// singleton
static let shared = UploadHelper()
func upload(fileUrl: URL, fileUploadUrl: String, uploadId: String) {
id = uploadId
//
// upload logic
//
upload?.progressBlock = progressBlock
upload?.resume()
}
}
I'm trying to implement the comment view controller.
I want using the auto layout via the storyboard.
My question is...
When I tap the input text..then keyboard will move up...but input text is not move up..
Keyboard is overlapping the text input..
Here is TableViewController.swift
import UIKit
import Parse
var commentUUID = [String]()
var commentOwner = [String]()
class CommentViewController: UIViewController, UITextViewDelegate, UITableViewDelegate, UITableViewDataSource{
//UI Objects
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var commentTextView: UITextView!
#IBOutlet weak var sendButton: UIButton!
var refresher = UIRefreshControl()
//values for reseting UI to default
var tableViewHeight : CGFloat = 0
var commentY : CGFloat = 0
var commentHeight : CGFloat = 0
//arryas to hold server data
var usernameArray = [String]()
var profileArray = [PFFile]()
var commentArray = [String]()
var dateArray = [NSDate?]()
//variable to hold keyboard frame
var keyboard = CGRect()
//page size
var page : Int32 = 15
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = .redColor()
//title at the top
self.navigationItem.title = "COMMENTS"
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(title: "back", style: .Plain, target: self, action: #selector(CommentViewController.back(_:)))
self.navigationItem.leftBarButtonItem=backButton
//swipe to go back
let backSwipe = UISwipeGestureRecognizer(target: self, action: #selector(CommentViewController.back(_:)))
backSwipe.direction=UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(backSwipe)
// catch notification if the keyboard is shown or hidden
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(CommentViewController.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(CommentViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
//disable button from the beginning
sendButton.enabled = false
//call function
alignment()
loadComments()
}
//I think it is not affect on the layout...
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
}
// preload func
override func viewWillAppear(animated: Bool) {
//hide bottom bar
self.tabBarController?.tabBar.hidden = true
}
// postload func
override func viewWillDisappear(animated: Bool) {
self.tabBarController?.tabBar.hidden = false
}
//func loading when keyboard is shown
func keyboardWillShow(notification : NSNotification){
//define keyboard frame size
keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue)!
//move UI up
UIView.animateWithDuration(0.4){ () -> Void in
self.tableView.frame.size.height = self.tableViewHeight - self.keyboard.height - self.commentTextView.frame.size.height + self.commentHeight
print("keyboard show")
self.commentTextView.frame.origin.y = self.commentY - self.keyboard.height - self.commentTextView.frame.size.height + self.commentHeight
self.sendButton.frame.origin.y = self.commentTextView.frame.origin.y
self.commentTextView.frame.origin.y = 250
}
}
//func loading when keyboard is hidden
func keyboardWillHide(notification : NSNotification){
//move UI down
UIView.animateWithDuration(0.4){() -> Void in
self.tableView.frame.size.height = self.tableViewHeight
self.commentTextView.frame.origin.y = self.commentY
self.sendButton.frame.origin.y = self.commentY
}
}
//alignment function
func alignment(){
let width = self.view.frame.size.width
let height = self.view.frame.size.height
tableView.frame = CGRectMake(0, 0, width, height - self.navigationController!.navigationBar.frame.size.height)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
commentTextView.layer.cornerRadius = commentTextView.frame.size.width / 50
//delegates
commentTextView.delegate = self
tableView.delegate = self
tableView.dataSource = self
//assign reseting values
tableViewHeight = tableView.frame.size.height
commentHeight = commentTextView.frame.size.height
commentY = commentTextView.frame.origin.y
}
//while writing something
func textViewDidChange(textView: UITextView) {
//disable button if entered no text
let spacing = NSCharacterSet.whitespaceAndNewlineCharacterSet()
//It shown when user entered type
if !commentTextView.text.stringByTrimmingCharactersInSet(spacing).isEmpty{
sendButton.enabled = true
}else {
sendButton.enabled = false
}
// + paragraph
if textView.contentSize.height > textView.frame.size.height && textView.frame.height < 130{
//find difference to add
let difference = textView.contentSize.height - textView.frame.size.height
//redefine frame of commentText
textView.frame.origin.y = textView.frame.origin.y - difference
textView.frame.size.height = textView.contentSize.height
//move up tableView
if textView.contentSize.height + keyboard.height + commentY >= tableView.frame.size.height {
tableView.frame.size.height = tableView.frame.size.height - difference
}
}
// - parapraph
else if textView.contentSize.height < textView.frame.size.height {
//find difference to deduct
let difference = textView.frame.size.height - textView.contentSize.height
//redefine frame of commentText
textView.frame.origin.y = textView.frame.origin.y + difference
textView.frame.size.height = textView.contentSize.height
//move down tableview
if textView.contentSize.height + keyboard.height + commentY > tableView.frame.size.height {
tableView.frame.size.height = tableView.frame.size.height + difference
}
}
}
//load comments function
func loadComments(){
//STEP 1. Count total comments in order to skip all except page size
let countQuery = PFQuery(className: "comments")
countQuery.whereKey("to", equalTo: commentUUID.last!)
countQuery.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
//if comments on the server for current post are more than (page size 15) implement pull to refresh func
if self.page < count {
self.refresher.addTarget(self, action: #selector(CommentViewController.loadMore), forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(self.refresher)
}
//STEP 2. Request last (page size 15) comments
let query = PFQuery(className: "comments")
query.whereKey("to", equalTo: commentUUID.last!)
query.skip = count - self.page
query.addAscendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({(objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//clean up
self.usernameArray.removeAll(keepCapacity: false)
self.profileArray.removeAll(keepCapacity: false)
self.commentArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepCapacity: false)
//find related object
for object in objects!{
self.usernameArray.append(object.objectForKey("username") as! String)
self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.commentArray.append(object.objectForKey("comment") as! String)
self.dateArray.append(object.createdAt)
self.tableView.reloadData()
//scroll to bottom
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.commentArray.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
}
}else {
print(error?.localizedDescription)
}
})
})
}
//Pagenation
func loadMore(){
//STEP 1. Count total comments in order to skip all except page size
let countQuery = PFQuery(className: "comments")
countQuery.whereKey("to", equalTo: commentUUID.last!)
countQuery.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
//self refresher
self.refresher.endRefreshing()
//remove refresher if loaded all comments
if self.page >= count {
self.refresher.removeFromSuperview()
}
//STEP2. Load more comments
if self.page < count {
//increase page to laod 30 as first paging
self.page = self.page + 15
//request existing comments from the server
let query = PFQuery(className: "comments")
query.whereKey("to", equalTo: commentUUID.last!)
query.skip = count - self.page
query.addAscendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({(objects:[PFObject]?, error:NSError?) -> Void in
if error==nil{
//clean up
self.usernameArray.removeAll(keepCapacity: false)
self.profileArray.removeAll(keepCapacity: false)
self.commentArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepCapacity: false)
//find related objects
for object in objects! {
self.usernameArray.append(object.objectForKey("username") as! String)
self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.commentArray.append(object.objectForKey("comments") as! String)
self.dateArray.append(object.createdAt)
self.tableView.reloadData()
}
}else {
print(error?.localizedDescription)
}
})
}
})
}
//Send Button Tapped
#IBAction func sendButtonTapped(sender: AnyObject) {
print("send tapped")
//STEP1. Add row in tableView
usernameArray.append(PFUser.currentUser()!.username!)
profileArray.append(PFUser.currentUser()?.objectForKey("profileImg") as! PFFile)
dateArray.append(NSDate())
commentArray.append(commentTextView.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()))
tableView.reloadData()
//STEP2. Send comment to server
let commentObj = PFObject(className: "comments")
commentObj["to"] = commentUUID.last
commentObj["username"] = PFUser.currentUser()?.username
commentObj["profileImg"] = PFUser.currentUser()?.valueForKey("profileImg")
commentObj["comment"] = commentTextView.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
commentObj.saveEventually()
//Scroll to bottom
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forItem: commentArray.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
//STEP 3. Reset UI
sendButton.enabled = false
commentTextView.text = ""
commentTextView.frame.size.height = commentHeight
commentTextView.frame.origin.y = sendButton.frame.origin.y
tableView.frame.size.height = self.tableViewHeight - self.keyboard.height - self.commentTextView.frame.size.height + self.commentHeight
}
//tableview
//cell numb
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return commentArray.count
}
//cell height
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
//Cell config
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//declaire cell
let cell = tableView.dequeueReusableCellWithIdentifier("commentCell") as! CommentTableViewCell
cell.usernameButton.setTitle(usernameArray[indexPath.row], forState: .Normal)
cell.usernameButton.sizeToFit()
cell.commentLabel.text = commentArray[indexPath.row]
profileArray[indexPath.row].getDataInBackgroundWithBlock({(data:NSData?, error:NSError?) -> Void in
cell.profileImagevView.image = UIImage(data: data!)
})
//calculate date
let from = dateArray[indexPath.row]
let now = NSDate()
let components : NSCalendarUnit = [.Second, .Minute, .Hour, .Day, .WeekOfMonth]
let difference = NSCalendar.currentCalendar().components(components, fromDate: from!, toDate: now, options: [])
if difference.second <= 0 {
cell.dateLabel.text = "now"
}
if difference.second > 0 && difference.minute == 0 {
cell.dateLabel.text = "\(difference.second)s"
}
if difference.minute > 0 && difference.hour == 0 {
cell.dateLabel.text = "\(difference.minute)m"
}
if difference.hour > 0 && difference.day == 0 {
cell.dateLabel.text = "\(difference.hour)h"
}
if difference.day > 0 && difference.weekOfMonth == 0 {
cell.dateLabel.text = "\(difference.day)d."
}
if difference.weekOfMonth > 0 {
cell.dateLabel.text = "\(difference.weekOfMonth)w."
}
cell.usernameButton.layer.setValue(indexPath, forKey: "index")
return cell
}
//clicked username button
#IBAction func usernameButtonTapped(sender: AnyObject) {
//call index of current button
let i = sender.layer.valueForKey("index") as! NSIndexPath
//Call cell to call further cell data
let cell = tableView.cellForRowAtIndexPath(i) as! CommentTableViewCell
//if user tapped on his username go home, else go guest
if cell.usernameButton.titleLabel?.text == PFUser.currentUser()?.username {
let home = self.storyboard?.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
self.navigationController?.pushViewController(home, animated: true)
}else {
guestname.append(cell.usernameButton.titleLabel!.text!)
let guest = self.storyboard?.instantiateViewControllerWithIdentifier("GuestHomeViewController") as! GuestHomeViewController
self.navigationController?.pushViewController(guest, animated: true)
}
}
//go back
func back(sender : UIBarButtonItem){
//push back
self.navigationController?.popViewControllerAnimated(true)
//clean comment uuid from holding information
if !commentUUID.isEmpty{
commentUUID.removeLast()
}
//clean comment owner from last holding information
if !commentOwner.isEmpty{
commentOwner.removeLast()
}
}
}
And It is my cell controller
import UIKit
class CommentTableViewCell: UITableViewCell {
#IBOutlet weak var profileImagevView: UIImageView!
#IBOutlet weak var usernameButton: UIButton!
#IBOutlet weak var commentLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
//default func
override func awakeFromNib() {
super.awakeFromNib()
//round profile
profileImagevView.layer.cornerRadius = profileImagevView.frame.size.width/2
profileImagevView.clipsToBounds = true
}
}
Two comments:
Use UIKeyboardDidShowNotification instead of UIKeyboardWillShowNotification
Do not modify frame directly when you're using auto layout. Just link your bottom layout constraint to the controller and change a constant value when needed.
Try this:
self.tableView.frame.size.height = self.view.frame.height - keyboard.height - self.tableView.frame.size.height
print("keyboard show")
self.commentTextView.frame = CGRect(self.commentTextView.frame.minX, self.commentTextView.frame.minY - keyboard.height, self.commentTextView.frame.width, self.commentTextView.frame.height)
self.sendButton.frame.origin.y = self.commentTextView.frame.origin.y
Edit
#Arsen's Answer makes much more sense and probably much easier, by the way :P But this should work the same.
I am displaying some data from JSON into my AccordionView. The problem is that my View thread completes executing before my Network Thread. So I Can't populate the Data which I get from the Array to my AccordionView.
So it shows that Array index out of range exception. I also tried using the reloadData Method But it is of no use.
I researched this issue and found about GCD. I can't figure out how to use GCD for this issue.
This is the AccordionView Library.
import UIKit
import Alamofire
import SwiftyJSON
var CourseIdinController : String!
var CourseDescriptionC: String!
var ChapNameC : [String] = []
var titleLabel : UILabel?
class CoursePageController: UIViewController {
#IBOutlet weak var courseDesc: UITextView!
var CourseName : String!
var ChapName : [String] = []
var LessonName : [String] = []
var myAccordionView : MKAccordionView?
override func viewDidLoad() {
super.viewDidLoad()
self.title = CourseName
self.courseDesc.text = CourseDescriptionC
self.courseDesc.setContentOffset(CGPointZero, animated: true)
view.bounds.size.height = 450.0
myAccordionView = MKAccordionView(frame: CGRectMake(0, 111, CGRectGetWidth(view.bounds), CGRectGetHeight(view.bounds)));
println(ChapNameC.count)
myAccordionView!.delegate = self;
myAccordionView!.dataSource = self;
view.addSubview(myAccordionView!);
getData()
getlData()
}
func getData(){
Alamofire.request(.GET, "http://www.wgve.com/index.php/capp/get_chapter_by_course_id/\(CourseIdinController)")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let catCount = json.count
for index in 0...catCount-1 {
let cname = json[index]["CHAPTER_NAME"].string
self.ChapName.append(cname!)
println(self.ChapName[index])
}
self.myAccordionView!.tableView?.reloadData()
}}
func getlData(){
Alamofire.request(.GET, "http://www.wgve.com/index.php/capp/get_lesson_by_course_id/\(CourseIdinController)")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let catCount = json.count
for index in 0...catCount-1 {
let cname = json[index]["LESSON_NAME"].string
self.LessonName.append(cname!)
}
self.myAccordionView!.tableView?.reloadData()
}}
func accordionView(accordionView: MKAccordionView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: nil)
//cell?.imageView = UIImageView(image: UIImage(named: "lightGrayBarWithBluestripe"))
// Background view
var bgView : UIView? = UIView(frame: CGRectMake(0, 0, CGRectGetWidth(accordionView.bounds), 50))
var bgImageView : UIImageView! = UIImageView(image: UIImage(named: "lightGrayBarWithBluestripe"))
bgImageView.frame = (bgView?.bounds)!
bgImageView.contentMode = UIViewContentMode.ScaleToFill
bgView?.addSubview(bgImageView)
cell?.backgroundView = bgView
// You can assign cell.selectedBackgroundView also for selected mode
cell?.textLabel?.text = LessonName[0]
return cell!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - Implemention of MKAccordionViewDatasource method
extension CoursePageController : MKAccordionViewDatasource {
func numberOfSectionsInAccordionView(accordionView: MKAccordionView) -> Int {
println(ChapName.count)
return ChapName.count
}
func accordionView(accordionView: MKAccordionView, numberOfRowsInSection section: Int) -> Int {
println(LessonName.count)
return LessonName.count
}
}
// MARK: - Implemention of MKAccordionViewDatasource method
extension CoursePageController : MKAccordionViewDatasource {
func numberOfSectionsInAccordionView(accordionView: MKAccordionView) -> Int {
return ChapName.count
}
func accordionView(accordionView: MKAccordionView, numberOfRowsInSection section: Int) -> Int {
return LessonName.count
}
}
// MARK: - Implemention of MKAccordionViewDelegate method
extension CoursePageController : MKAccordionViewDelegate {
func accordionView(accordionView: MKAccordionView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 50
}
func accordionView(accordionView: MKAccordionView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func accordionView(accordionView: MKAccordionView, viewForHeaderInSection section: Int, isSectionOpen sectionOpen: Bool) -> UIView? {
var view : UIView! = UIView(frame: CGRectMake(0, 0, CGRectGetWidth(accordionView.bounds), 50))
// Background Image
var bgImageView : UIImageView = UIImageView(frame: view.bounds)
bgImageView.image = UIImage(named: ( sectionOpen ? "grayBarSelected" : "grayBar"))!
view.addSubview(bgImageView)
// Arrow Image
var arrowImageView : UIImageView = UIImageView(frame: CGRectMake(15, 15, 20, 20))
arrowImageView.image = UIImage(named: ( sectionOpen ? "close" : "open"))!
view.addSubview(arrowImageView)
// Title Label
titleLabel = UILabel(frame: CGRectMake(50, 0, CGRectGetWidth(view.bounds) - 120, CGRectGetHeight(view.bounds)))
if (ChapName.count != 0) {
let count = ChapName.count
for index in 0...count-1 {
titleLabel!.text = self.ChapName[index]
}
}
titleLabel!.textColor = UIColor.whiteColor()
view.addSubview(titleLabel!)
return view
}
}
Note:
It shows me error because the ChapName & lesson name array are Empty. If i Hardcode the values. It prints both the array's in my console.
May be the accordionView doesn't support the reload Method?
or Where am I Going Wrong ?
UPDATE
numberOfSectionsInAccordionView throws Error like this fatal error: Can't form Range with end < start which on line 233
Refer this link
2.The titleLabel returns only the last item in the Array. I don't know Why ?
You should make sure all your json data is successfully stored into an array "accordionDataArray" before you attempt to reload it.
ATTENTION : make sure that the datasource methods numberOfRowsInSection and numberOfSectionsInAccordionView rely on the accordionDataArray content (or whatever Array you have) to determine the number of sections and rows of the accordion.
P.S. Anyway Why would you reload the content of the accordion in a loop?
Do this instead:
for index in 0...catCount-1 {
let cname = json[index]["CHAPTER_NAME"].string
self.ChapName.append(cname!)
println(self.ChapName[index])
}
self.myAccordionView!.tableView?.reloadData()
UPDATE:
To get passed the fact that ChapName & lessonName array are asked for some value when they are Empty , you need to check that like this whenever they occur:
if (ChapName.count != 0) {
titleLabel!.text = self.ChapName[0]
}
if (LessonName.count != 0){
// do something
}
Make sure you update your code and test. In the method:
func accordionView(accordionView: MKAccordionView, viewForHeaderInSection section: Int, isSectionOpen sectionOpen: Bool) -> UIView? {
[...]
if (ChapName.count != 0) {
titleLabel!.text = self.ChapName[0]
}
[...]
}
UPDATE 2: The library you are using doesn't seem to allow the number of sections to be 0. I suggest you contact the library's owner if you can't make sure that the number of section is at least 1.
Regarding to your other issue with titleLabel being always the last item of the array you need to change this:
if (ChapName.count != 0) {
let count = ChapName.count
for index in 0...count-1 {
titleLabel!.text = self.ChapName[index]
}
}
into this:
titleLabel!.text = self.ChapName[section]
About why the code above does not work:
numberOfRowsInSection returns a hardcoded value, regardless of the JSON response.
About stopping the UI thread:
We are somewhat glad you can't do that. GCD helps you to share processor time efficiently, the exact opposite of holding the UI thread hostage. Image the consequence if somehow your network thread would hang. What would happen to the UI thread? Don't do it.
Solutions: Your make your UI thread clever enough to handle absence of datum. i.e. do not return hardcoded counts for sections or cells. When you receive data, update your records (the model), send a refresh message to the UI (such as reloadData in UITableView).