In swift - UITableview gets jerky while scrolling - ios

Am make a tableview listing, data loading dynamically and a image
contain in cell and some text. When it scroll gets jerking and
irritating the users
Any idea for improving the table view performance?
class MainViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var listTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.listTable.delegate = self
self.listTable.dataSource = self
self.listTable.separatorStyle = .None
self.listTable.registerNib(UINib(nibName: "cellOne", bundle: nil), forCellReuseIdentifier: "cellOne")
self.listTable.registerNib(UINib(nibName: "cellTwo", bundle: nil), forCellReuseIdentifier: "cellTwo")
self.listTable.registerNib(UINib(nibName: "cellThree", bundle: nil), forCellReuseIdentifier: "cellThree")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int{
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if pageIndex == 7 {
return 120
} else if indexPath.row == 0 {
return 280
} else if (indexPath.row == 5 || indexPath.row == 4) {
return 200
} else if (indexPath.row == 8 || indexPath.row == 11) {
return 160
}
return 80
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 50
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let listArray = self.listDataArray[indexPath.row]
if indexPath.row == 0 && pageIndex != 7 {
var cell = tableView.dequeueReusabl eCellWithIdentifier("cellOne") as! cellOne!
if cell == nil {
tableView.registerClass(cellOne.classForCoder(), forCellReuseIdentifier: "cellOne")
cell = cellOne(style: UITableViewCellStyle.Default, reuseIdentifier: "cellOne")
}
cell.headLbl.text = "\(listArray["title"]!)".html2String
cell.addImageView.userInteractionEnabled = true
let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
cell.addImageView.addGestureRecognizer(guster)
cell.addImageView.tag = 1
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
let u = listArray["image"] as! String
let url = NSURL(string: u)
if url != nil {
let data = NSData(contentsOfURL: url!)
if data != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageViews.image = UIImage(data: data!)
})
self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
}
}
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageViews.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
})
}
})
if Constants.sharedInstance.addData["1"] != nil {
cell.addImageView.image = UIImage(data: Constants.sharedInstance.addData["1"] as! NSData)
}
return cell
} else if indexPath.row == 4 && pageIndex != 7 {
var cell = tableView.dequeueReusableCellWithIdentifier("cellTwo") as! cellTwo!
if cell == nil {
tableView.registerClass(cellTwo.classForCoder(), forCellReuseIdentifier: "cellTwo")
cell = cellTwo(style: UITableViewCellStyle.Default, reuseIdentifier: "cellTwo")
}
cell.headLbl.text = "\(listArray["title"]!)".html2String
cell.addImageView.userInteractionEnabled = true
let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
cell.addImageView.addGestureRecognizer(guster)
cell.addImageView.tag = 2
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
let u = listArray["image"] as! String
let url = NSURL(string: u)
if url != nil {
let data = NSData(contentsOfURL: url!)
if data != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageViews.image = UIImage(data: data!)
})
self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
}
}
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageViews.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
})
}
})
if Constants.sharedInstance.addData["2"] != nil {
cell.addImageView.image = UIImage(data: Constants.sharedInstance.addData["2"] as! NSData)
}
return cell
} else if indexPath.row == 5 && pageIndex != 7 {
var cell = tableView.dequeueReusableCellWithIdentifier("cellThree") as! cellThree!
if cell == nil {
tableView.registerClass(cellThree.classForCoder(), forCellReuseIdentifier: "cellThree")
cell = cellThree(style: UITableViewCellStyle.Default, reuseIdentifier: "cellThree")
}
cell.headLbl.text = "\(listArray["title"]!)".html2String
cell.addImageView.userInteractionEnabled = true
let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
cell.addImageView.addGestureRecognizer(guster)
cell.addImageView.tag = 3
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
let u = listArray["image"] as! String
let url = NSURL(string: u)
if url != nil {
let data = NSData(contentsOfURL: url!)
if data != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageViews.image = UIImage(data: data!)
})
self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
}
}
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageViews.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
})
}
})
if Constants.sharedInstance.addData["3"] != nil {
cell.addImageView.image = UIImage(data: Constants.sharedInstance.addData["3"] as! NSData)
}
return cell
} else {
cell.backgroundColor = UIColor.groupTableViewBackgroundColor()
let container = UIView()
container.frame = CGRectMake(2, 1, tableView.frame.width-4, 78)
container.backgroundColor = UIColor.whiteColor()
cell.addSubview(container)
let headerLbl = UILabel()
headerLbl.backgroundColor = UIColor.clearColor()
headerLbl.frame = CGRectMake(120, 1, self.view.frame.width-130, 76)
headerLbl.numberOfLines = 0
headerLbl.font = UIFont(name: "TelegramHead", size: 18)
headerLbl.text = "\(listArray["title"]!)".html2String
headerLbl.lineBreakMode = NSLineBreakMode.ByCharWrapping
let imageView = UIImageView()
imageView.frame = CGRectMake(5, 5, 100, 68)
imageView.image = UIImage(named: "place_holder.jpg")
imageView.contentMode = UIViewContentMode.ScaleAspectFill
imageView.clipsToBounds = true
let blackLayerView = UIView();
blackLayerView.frame = CGRectMake(0, 0, 0, 0);
blackLayerView.backgroundColor = UIColor.blackColor();
blackLayerView.alpha = 0.4;
container.addSubview(imageView)
container.addSubview(blackLayerView);
container.addSubview(headerLbl)
if pageIndex == 7 {
container.frame = CGRectMake(2, 1, tableView.frame.width-4, 118)
imageView.frame = CGRectMake(5, 5, container.frame.width-10, 108)
blackLayerView.frame = imageView.frame;
headerLbl.frame = imageView.frame//CGRectMake(10, 5, self.view.frame.width-20, 100)
headerLbl.textColor = UIColor.whiteColor()
headerLbl.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2)
} else if (indexPath.row == 8 || indexPath.row == 11) && pageIndex != 7 {
let add = UIImageView()
add.frame = CGRectMake(2, 82, tableView.frame.width-4, 76)
//add.contentMode = UIViewContentMode.ScaleAspectFit;
cell.addSubview(add)
add.userInteractionEnabled = true
let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
add.addGestureRecognizer(guster)
if Constants.sharedInstance.addData["4"] != nil && indexPath.row == 8{
add.image = UIImage(data: Constants.sharedInstance.addData["4"] as! NSData)
add.tag = 4
}
if Constants.sharedInstance.addData["5"] != nil && indexPath.row == 11{
add.image = UIImage(data: Constants.sharedInstance.addData["5"] as! NSData)
add.tag = 5
}
}
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
let u = listArray["image"] as! String
let url = NSURL(string: u)
if url != nil {
let data = NSData(contentsOfURL: url!)
if data != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
imageView.image = UIImage(data: data!)
self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
})
}
}
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
imageView.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
})
}
})
}
return cell
}
}
I do get the required functionality. But there is a very unnatural jerk on the TableView which results in a bad User experience.

There are a number of improvements you could make to this code to improve performance.
Don't register cells in cellForRow
Define specific custom classes for each cell and register them for reuse in viewDidLoad
Reduce the logic used in cellForRowAt
At the start of cellForRowAt you create a new instance of a cell and later add subviews to it if none of the other cases match. there is no reuse for this cell. Always reuse. you'll use less resources
I have built a complex table view before and created functions that configure and return reusble cells for the particular indexPath.
class LargeImageCell: UITableViewCell {
var imageView: UIImageView()
// in init, init the cell, add imageview as subview, setup constraints
func setContent(imageURL: URL) {
// load image from cache or fetch from network in background
}
}
// in tableview/view controller
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
return self.getLargeImageCell(indexPath: indexPath)
// all other cases
}
}
This is just roughly how I implemented mine, just try to reduce the amount of logic in the cell and I've alwayss found it best to create custom cells rather than adding them in cellForRowAt, let the cell configure itself
func getLargeImageCell(indexPath: NSIndexPath) -> UITableViewCell {
let data = self.data[indexPath.row] // model from db
var cell: UITableViewCell
if let c = self.tableView.dequeueReusableCellWithIdentifier("largeImageCell") as? LargeImageCell {
cell = c
} else {
cell = LargeImageCell(style: UITableViewCellStyle.Default, reuseIdentifier: "largeImageCell")
}
cell.setContent(imageURL: data.imageURL)
return cell
}

Related

Video preload in uicollectionview in swift

I am developing Instagram-like an app that has insta reels-like UI where videos are played in the list but what happens now is when cell comes on scree video buffers or loads and it takes 1-2 sec to load because of that uicollectionview scroll lags.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let currentItem = appData[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "swipablePostCollectionViewCell", for: indexPath) as! swipablePostCollectionViewCell
preFetchNextItems(currentIndexPath: indexPath)
if currentItem["video_url"] != "" && currentItem["video_url"] != nil {
cell.postVideoView.isHidden = false
cell.postImageView.isHidden = true
cell.postVideoView.tag = indexPath.item
DispatchQueue.main.async {
if let imageURL: URL = URL(string: currentItem["video_url"]!) {
if indexPath.item == 0 {
let item = VersaPlayerItem(url: imageURL)
cell.postVideoView.set(item: item)
cell.postVideoView.play()
} else {
if let newItem = self.prefetchedData[indexPath.item - 1] as? VersaPlayerItem {
cell.postVideoView.set(item: newItem)
}
}
cell.postVideoView.controls?.behaviour.shouldHideControls = false
cell.postVideoView.isPipModeEnabled = false
cell.postVideoView.autoplay = false
cell.postVideoView.renderingView.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
cell.postVideoView.layer.backgroundColor = UIColor.black.cgColor
cell.postVideoView.use(controls: cell.controls)
cell.postVideoView.playbackDelegate?.startBuffering(player: cell.postVideoView.player)
cell.tag = indexPath.item
}
}
return cell
} else {
cell.postVideoView.isHidden = true
cell.postImageView.isHidden = false
if let imageURL: URL = URL(string: currentItem["image_url"] ?? "") {
cell.postImageView.sd_setImage(with: imageURL, placeholderImage: UIImage(named: "error"), options: .delayPlaceholder, context: nil)
cell.postImageView.contentMode = .scaleAspectFill
cell.postImageView.clipsToBounds = true
}
return cell
}
}
And for prefetch
func preFetchNextItems(currentIndexPath: IndexPath?) {
if currentIndexPath?.item == 0 {
for i in 1...4 {
let indexP = IndexPath(item: i, section: 0)
arrFetchedIndexPaths.append(indexP)
lastFetchedIndexPath = indexP
GetDataForPrefetchIndexPath()
}
} else {
let newIndePath = IndexPath(item: lastFetchedIndexPath!.item + 1, section: lastFetchedIndexPath!.section)
arrFetchedIndexPaths.append(newIndePath)
lastFetchedIndexPath = newIndePath
GetDataForPrefetchIndexPath()
}
print("Prefetch indexpath - ", arrFetchedIndexPaths)
}
func GetDataForPrefetchIndexPath() {
if appData.count > lastFetchedIndexPath!.item {
let currentItem = appData[lastFetchedIndexPath!.item]
if currentItem["video_url"] != "" && currentItem["video_url"] != nil {
if let imageURL: URL = URL(string: currentItem["video_url"]!) {
let newItem = VersaPlayerItem(url: imageURL)
prefetchedData.append(newItem)
}
} else {
if let imageURL: URL = URL(string: currentItem["image_url"] ?? "") {
// let newImage = sd_setImage(with: imageURL, placeholderImage: UIImage(named: "error"), options: .delayPlaceholder, context: nil)
prefetchedData.append(imageURL)
}
}
for item in prefetchedData {
print(item)
}
}
}
What i need is on first cell next 4 cell video should preloaded and after user scrolls next cell video should load
so that when user scrolls to next cell video will be preloaded and played without buffer.

swift3 uitableView reload makes screen blink

I am using UITableview and it looks similar with the feed of Instagram. The issue I have
I have a like function in each tableviewCell
when tap like button, it needs to update the screen and the screen blinks
In tableview cellForRowAt function, I have a network call to check the like and its number.
Please let me know if there is a way to avoid this blink. Should I avoid network call in this function or is there any other way?
U can see some part of my code below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! StoryReviewTableViewCell
let review = ReviewArray[indexPath.row]
// 프로필 이미지랑 닉네임 설정
if let user = review.creator {
let nickname = user.getProperty("nickname") as! String
cell.profileName.text = nickname
if let profileURL = user.getProperty("profileURL") {
if profileURL is NSNull {
cell.profileImage.image = #imageLiteral(resourceName: "user_profile")
} else {
let url = URL(string: profileURL as! String)
DispatchQueue.main.async {
cell.profileImage.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "imageLoadingHolder"), options: [.transition(.fade(0.2))], progressBlock: nil, completionHandler: nil)
}
}
}
} else {
// 삭제된 유저의 경우
cell.profileName.text = "탈퇴 유저"
cell.profileImage.image = #imageLiteral(resourceName: "user_profile")
}
// 장소 이름
if let store = ReviewArray[indexPath.row].store {
cell.storeName.text = "장소: \(String(describing: store.name!))"
} else {
cell.storeName.text = "가게 이름"
}
// 라이크버튼 설정 - 라이크 모양은 여기서 컨트롤, delegate에서 user 라이크 컨트롤
DispatchQueue.global(qos: .userInteractive).async {
let likeStore = Backendless.sharedInstance().data.of(ReviewLikes.ofClass())
let dataQuery = BackendlessDataQuery()
let objectID = review.objectId!
let userID = UserManager.currentUser()!.objectId!
// print("objectID & userID: \(objectID) & \(userID)")
// 여기서 by가 현재 유저의 objectId이어야 하고, to는 이 리뷰의 objectId이어야 한다
dataQuery.whereClause = "by = '\(userID)' AND to = '\(objectID)'"
DispatchQueue.main.async {
likeStore?.find(dataQuery, response: { (collection) in
let likes = collection?.data as! [ReviewLikes]
// 하트를 안 눌렀을 때
if likes.count == 0 {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}
} else {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}
}
}, error: { (Fault) in
print("라이크 불러오기에서 에러: \(String(describing: Fault?.description))")
})
}
// 좋아요 개수 세기
let countQuery = BackendlessDataQuery()
// to가 story의 objectID와 일치하면 땡
countQuery.whereClause = "to = '\(objectID)'"
let queryOptions = QueryOptions()
queryOptions.pageSize = 1
countQuery.queryOptions = queryOptions
DispatchQueue.global(qos: .userInteractive).async {
let matchingLikes = likeStore?.find(countQuery)
let likeNumbers = matchingLikes?.totalObjects
DispatchQueue.main.async {
if likeNumbers == 0 {
cell.likeLabel.text = "라이크 없음 ㅠ"
} else {
cell.likeLabel.text = "\(String(describing: likeNumbers!))개의 좋아요"
}
}
}
}
// 리뷰 평점 배당
cell.ratingView.value = review.rating as! CGFloat
// 리뷰 바디
cell.reviewBody.text = review.text
// 코멘트 개수 받아오기
DispatchQueue.global(qos: .userInteractive).async {
// 댓글수 찾기
let tempStore = Backendless.sharedInstance().data.of(ReviewComment.ofClass())
let reviewId = review.objectId!
let dataQuery = BackendlessDataQuery()
// 이 리뷰에 달린 댓글 모두 몇 개인지 찾기
dataQuery.whereClause = "to = '\(reviewId)'"
DispatchQueue.main.async {
tempStore?.find(dataQuery, response: { (collection) in
let comments = collection?.data as! [ReviewComment]
cell.replyLabel.text = "댓글 \(comments.count)개"
}, error: { (Fault) in
print("서버에서 댓글 얻어오기 실패: \(String(describing: Fault?.description))")
})
}
}
cell.timeLabel.text = dateFormatter.string(from: review.created! as Date)
return cell
}
U can see the button action here
#IBAction func likeButtonClicked(_ sender: UIButton) {
likeButton.isUserInteractionEnabled = false
// delegate action
delegate?.actionTapped(tag: likeButton.tag)
// image change
if sender.image(for: .normal) == #imageLiteral(resourceName: "like_bw") {
UIView.transition(with: sender, duration: 0.2, options: .transitionCrossDissolve, animations: {
sender.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}, completion: nil)
self.likeButton.isUserInteractionEnabled = true
} else {
UIView.transition(with: sender, duration: 0.2, options: .transitionCrossDissolve, animations: {
sender.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}, completion: nil)
self.likeButton.isUserInteractionEnabled = true
}
}
This is part of my delegate function which reload only for the row
func changeLike(_ row: Int, _ alreadyLike: Bool, completionHandler: #escaping (_ success:Bool) -> Void) {
let selectedReview = ReviewArray[row]
let reviewId = selectedReview.objectId
// 그냥 유저 객체로 비교는 안되고 objectId로 체크를 해야 함
let objectID = Backendless.sharedInstance().userService.currentUser.objectId
let dataStore = Backendless.sharedInstance().data.of(ReviewLikes.ofClass())
// 좋아요 - alreadyLike가 true이면
if !alreadyLike {
// 객체 생성
let like = ReviewLikes()
like.by = objectID! as String
like.to = reviewId
dataStore?.save(like, response: { (response) in
DispatchQueue.main.async {
let indexPath = IndexPath(row: row, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
let cell = self.tblView.cellForRow(at: IndexPath(row: index, section: 0)) as! UITableViewCell
cell.btnLike.setImage(UIImage(named: (isReviewed! ? IMG_LIKE : IMG_UNLIKE)), for: .normal)
Just update the cell, instead of reloading whole data in the tableview.
Try to call data on button click instead of cellForRowAtIndexpath and use that button which clicked instead of reload data
#IBAction func likeButtonClicked(_ sender: UIButton) {
let buttonPosition:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
let cell = tableView.cellForRow(at: indexPath) as! StoryReviewTableViewCell
DispatchQueue.main.async {
likeStore?.find(dataQuery, response: { (collection) in
let likes = collection?.data as! [ReviewLikes]
// 하트를 안 눌렀을 때
if likes.count == 0 {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}
} else {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}
}
}, error: { (Fault) in
print("라이크 불러오기에서 에러: \(String(describing: Fault?.description))")
})
}

Swift Cells Not Displaying

I have a profile page that is made up of two custom tableview cells. The first custom cell is the user's info. The second custom cell is the user's friend. The first row is the user's info, and all of the cells after that are the user's friends. My code worked in Xcode 6, but stopped working after the update.
Problem: A user with 2 friends, their profile page should have a table with three cells: 1 user info cell, 2 friend cells. However, the first and second cell aren't showing. Only the third cell is showing.
Clarification: There should be three cells. Cell 1 is not showing. Cell 2 is not showing. But Cell 3 is showing. Cell 1 is the user's info. Cell 2 is one friend. Cell 3 is another friend.
Here's my code:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendList.count + 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 0{
return 182.0
}else{
return 95.0
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row != 0{
let cell = tableView.dequeueReusableCellWithIdentifier("friendCell", forIndexPath: indexPath) as! ProfileFriendTableViewCell
let friend = friendList[indexPath.row - 1]
cell.nameLabel.text = friend[1]
cell.usernameLabel.text = friend[2]
cell.schoolLabel.text = friend[3]
cell.sendRequestButton.tag = indexPath.row
var profileImageExists = false
if profileImages != nil{
for profileImage in profileImages{
if profileImage.forUser == friend[2]{
profileImageExists = true
cell.friendImageProgress.hidden = true
cell.profilePic.image = UIImage(data: profileImage.image)
UIView.animateWithDuration(0.2, animations: {
cell.profilePic.alpha = 1
})
}
}
}else if loadingImages == true{
profileImageExists = true
cell.friendImageProgress.hidden = true
cell.profilePic.image = UIImage(named: "profileImagePlaceholder")
UIView.animateWithDuration(0.2, animations: {
cell.profilePic.alpha = 1
})
}
if profileImageExists == false{
if Reachability.isConnectedToNetwork() == true{
let query = PFUser.query()
query?.getObjectInBackgroundWithId(friend[0], block: { (object, error) -> Void in
if error == nil{
if let object = object as? PFUser{
let friendProfilePicture = object.objectForKey("profileImage") as? PFFile
friendProfilePicture?.getDataInBackgroundWithBlock({ (data, error) -> Void in
if data != nil{
let image = UIImage(data: data!)
cell.profilePic.image = image
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
let newProfileImage = NSEntityDescription.insertNewObjectForEntityForName("ProfileImageEntity", inManagedObjectContext: managedObjectContext) as! ProfileImage
newProfileImage.forUser = friend[2]
newProfileImage.image = UIImagePNGRepresentation(image!)
do{
try managedObjectContext.save()
}catch _{
print("insert error")
}
}
}else{
cell.friendImageProgress.hidden = true
cell.profilePic.image = UIImage(named: "profileImagePlaceholder")
}
}, progressBlock: { (progress: Int32) -> Void in
let percent = progress
let progressPercent = Float(percent) / 100
cell.friendImageProgress.progress = progressPercent
cell.friendImageProgress.hidden = true
})
}
}
})
}
else{
cell.friendImageProgress.hidden = true
cell.profilePic.image = UIImage(named: "profileImagePlaceholder")
}
}
return cell
}else{
let cell = tableView.dequeueReusableCellWithIdentifier("profileTopCell", forIndexPath: indexPath) as! ProfileTableViewCell
var profileImageExists = false
if profileImages != nil{
for profileImage in profileImages{
if profileImage.forUser == PFUser.currentUser()!.username!{
profileImageExists = true
cell.profilePic.image = UIImage(data: profileImage.image)
}
}
}else if loadingImages == true{
profileImageExists = true
cell.profilePic.image = UIImage(named: "profileImagePlaceholder")
}
if profileImageExists == false{
if Reachability.isConnectedToNetwork() == true{
let profilePicture = PFUser.currentUser()!.objectForKey("profileImage") as? PFFile
profilePicture?.getDataInBackgroundWithBlock({ (data, error) -> Void in
if data != nil{
let image = UIImage(data: data!)
cell.profilePic.image = image
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
let newProfileImage = NSEntityDescription.insertNewObjectForEntityForName("ProfileImageEntity", inManagedObjectContext: managedObjectContext) as! ProfileImage
newProfileImage.forUser = PFUser.currentUser()!.username!
newProfileImage.image = UIImagePNGRepresentation(image!)
do{
try managedObjectContext.save()
}catch _{
print("insert error")
}
}
}else{
cell.profilePic.image = UIImage(named: "profileImagePlaceholder")
}
})
}else{
cell.profilePic.image = UIImage(named: "profileImagePlaceholder")
}
}
cell.nameLabel.text = PFUser.currentUser()!.objectForKey("Name") as? String
cell.usernameLabel.text = PFUser.currentUser()!.objectForKey("username") as? String
let friendNumber = PFUser.currentUser()!.objectForKey("numberOfFriends") as? Int
if friendNumber != 1{
cell.numberOfFriendsLabel.text = "\(friendNumber!) Friends"
}else{
cell.numberOfFriendsLabel.text = "1 Friend"
}
return cell
}
}
Try to use estimatedRowHeightand rowHeight = UITableViewAutomaticDimension on your viewDidLoad or viewWillAppear and on heightForRowAtIndexPath return UITableViewAutomaticDimension, remember to put constraints on your custom cell, so these can work properly.
Thanks to Jeremy Andrews (https://stackoverflow.com/a/31908684/3783946), I found the solution:
"All you have to do is go to file inspector - uncheck size classes - there will be warnings etc.run and there is the data - strangely - go back to file inspector and check "use size classes" again, run and all data correctly reflected. Seems like in some cases the margin is set to negative."
It was just a bug.

Loading indicator until tableView loads

I am using Parse to load some data for title , http req for ImageView and ratingView. I also have a segment control view with 2 options.
I put an indicator where the image is presented , but when I scroll down
or when I press another segment(different movies) I get for 1-2 seconds the previous image , ratingView , titles and they change after this instantly. And obviously not all together. Can I somehow sync the changes or give an indicator 'waiting to load' until everything loads ?
func segmentView(segmentView: SMSegmentView, didSelectSegmentAtIndex index: Int) {
self.currentSegmentSelected = index
loadData().findObjectsInBackgroundWithBlock { [weak self] objects, error in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
self?.loadObjects()
})}
}
}
queryForTable(Parse)
override func queryForTable() -> PFQuery {
let query:PFQuery = PFQuery(className:"Movies")
if(objects?.count == 0)
{
query.cachePolicy = PFCachePolicy.CacheThenNetwork
}
if self.currentSegmentSelected == 0 {
query.whereKey("genres", containsString: "Adventure")
}
else if currentSegmentSelected == 1 {
query.whereKey("genres", containsString: "Comedy")
}
return query
}
cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier:String = "cell"
var cell:TableCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? TableCell
if(cell == nil) {
cell = TableCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier)
}
//??? indicator.startAnimating()
//??? indicator.backgroundColor = UIColor.whiteColor()
if let pfObject = object {
PFCloud.callFunctionInBackground("averageStars", withParameters: ["movieId":pfObject["movieId"]]) {
(response: AnyObject?, error: NSError?) -> Void in
if let rating = response as! Float? {
cell?.rating.rating = rating
}}
if let movieId = pfObject["movieId"] as? Int {
if RecommendedSet.ratings[movieId] == nil {
cell?.userRating.rating = 0
} else {
cell?.userRating.rating = RecommendedSet.ratings[movieId]!
}
}
cell?.title?.text = pfObject["title"] as? String
if let Id = pfObject["tmdbId"] as? Int {
Alamofire.request(.GET, timdb , parameters: ["api_key": APIkey])
.responseJSON { response in
if let JSON...
let imgData: NSData = NSData(contentsOfURL: imgURL)!
dispatch_async(dispatch_get_main_queue(), {
cell?.posterView?.image = UIImage(data: imgData)
//??? self.indicator.stopAnimating()
//??? self.indicator.hidesWhenStopped = true
})
}}}}}}}
return cell
}

Hide custom ViewTableCell Button for a specific cell

I'm facing a problem with a UITableViewCell.
When I generate my tableCells, I check if a value is equal to something and if so, I hide a custom button I made in my prototype cell. The problem is, the button with the identifier I hide is hidden in every Cell and not the current one only...
I looked around but can't find something to do... Could you help ? Here's my code, look at the line if currentPost.valueForKey... == "noLink". Thanks !
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! PostTableViewCell
var currentCell = indexPath
var currentPost = post[indexPath.row]
var currentUser = followedInfo[currentPost.valueForKey("userID") as! String]
cell.username.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
cell.userProfil.layer.cornerRadius = 0.5 * cell.userProfil.bounds.size.width
cell.username.setTitle(currentUser?.valueForKey("username") as? String, forState: UIControlState.Normal)
cell.postArtist.text = currentPost.valueForKey("artistName") as? String
cell.postDescription.text = currentPost.valueForKey("postDescription") as? String
cell.postTitle.text = currentPost.valueForKey("songName") as? String
cell.postTime.text = "7J"
cell.postDescription.sizeToFit()
if currentPost.valueForKey("itunesLink") as? String == "noLink" {
cell.postItunes.hidden = true;
}else{
cell.postItunes.addTarget(self, action: "getToStore:", forControlEvents: .TouchUpInside)
}
if currentPost.valueForKey("previewLink") as? String == "noPreview" {
cell.postPlay.alpha = 0;
}else{
cell.postPlay.addTarget(self, action: "getPreview:", forControlEvents: .TouchUpInside)
}
if currentPost.valueForKey("location") as? String == "noLocalisation" {
cell.postLocation.text = "Inconnu"
}else{
cell.postLocation.text = currentPost.valueForKey("location") as? String
}
if currentUser?.valueForKey("profilePicture")! != nil{
var pictureFile: AnyObject? = currentUser?.valueForKey("profilePicture")!
pictureFile!.getDataInBackgroundWithBlock({ (imageData, error) -> Void in
var theImage = UIImage(data: imageData!)
cell.userProfil.image = theImage
})
}
if currentPost.valueForKey("coverLink") as? String == "customImage" {
var profilPictureFile: AnyObject? = currentPost.valueForKey("postImage")
profilPictureFile!.getDataInBackgroundWithBlock { (imageData , imageError ) -> Void in
if imageError == nil{
let image = UIImage(data: imageData!)
cell.postPicture.image = image
}
}
}else{
if currentPost.valueForKey("coverLink") as? String == "noCover"{
var cover = UIImage(named: "noCover")
cell.postPicture.image = cover
}else{
var finalURL = NSURL(string: currentPost.valueForKey("coverLink") as! String)
let request: NSURLRequest = NSURLRequest(URL: finalURL!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
var image = UIImage(data: data)
cell.postPicture.image = image
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
}
return cell;
}
You are not setting the bottom back to visible if you want it to show, because you are reusing the cell the last setup is part of the cell, so as soon as you set it to hidden to true it will still hidden until you set hidden back to false, the best way to use reusable cell is to always set all the options to what you want to be for that cell to avoid the setting to be as the old cell.
if currentPost.valueForKey("itunesLink") as? String == "noLink" {
cell.postItunes.hidden = true;
}else{
cell.postItunes.hidden = false;
cell.postItunes.addTarget(self, action: "getToStore:", forControlEvents: .TouchUpInside)
}

Resources