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))")
})
}
Related
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.
There are users being downloaded from firebase and displayed on a UITableView where the cells are selectable and once selected will have a check mark. So from firebase is it is asynchronously downloaded so I think this could be the start of me solving the problem but not sure. When selecting lets say two cells when the view appears and then you begin scrolling through the list other cells will appear to be selected when the user did not select them. Below will be code and pictures of the occurrence.
Firebase Call
func getTableViewData() {
Database.database().reference().child("Businesses").queryOrdered(byChild: "businessName").observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
if(key == self.loggedInUser?.uid) {
print("Same as logged in user, so don't show!")
} else {
if let locationValue = snapshot.value as? [String: AnyObject] {
let lat = Double(locationValue["businessLatitude"] as! String)
let long = Double(locationValue["businessLongitude"] as! String)
let businessLocation = CLLocation(latitude: lat!, longitude: long!)
let latitude = self.locationManager.location?.coordinate.latitude
let longitude = self.locationManager.location?.coordinate.longitude
let userLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let distanceInMeters: Double = userLocation.distance(from: businessLocation)
let distanceInMiles: Double = distanceInMeters * 0.00062137
let distanceLabelText = String(format: "%.2f miles away", distanceInMiles)
var singleChildDictionary = locationValue
singleChildDictionary["distanceLabelText"] = distanceLabelText as AnyObject
singleChildDictionary["distanceInMiles"] = distanceInMiles as AnyObject
self.usersArray.append(singleChildDictionary as NSDictionary)
self.usersArray = self.usersArray.sorted {
!($0?["distanceInMiles"] as! Double > $1?["distanceInMiles"] as! Double)
}
}
//insert the rows
//self.followUsersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
self.listedBusiness.reloadData()
}
}) { (error) in
print(error.localizedDescription)
}
}
TableView Setup
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != ""{
return filteredUsers.count
}
return self.usersArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomerAddSelectedBusinessesCell
var user : NSDictionary?
if searchController.isActive && searchController.searchBar.text != ""{
user = filteredUsers[indexPath.row]
} else {
user = self.usersArray[indexPath.row]
}
if cell.isSelected == true {
var user = self.usersArray[indexPath.row]
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
print("No access")
cell.businessName.text = String(user?["businessName"] as! String)
cell.businessStreet.text = String(user?["businessStreet"] as! String)
cell.businessCity.text = String(user?["businessCity"] as! String)
//cell.selectedCell.image = UIImage(named: "cellSelected")
let businessProfilePicture = String(user?["profPicString"] as! String)
if (businessProfilePicture.count) > 0 {
let url = URL(string: (businessProfilePicture))
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
}
} else {
let image = UIImage(named: "default")?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
//cell.profileImage.image =
//cell.businessDistance.text = String(user?["distanceLabelText"] as! String)
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
print("****Called")
cell.businessName.text = String(user?["businessName"] as! String)
cell.businessStreet.text = String(user?["businessStreet"] as! String)
cell.businessCity.text = String(user?["businessCity"] as! String)
cell.businessDistance.text = String(user?["distanceLabelText"] as! String)
//cell.selectedCell.image = UIImage(named: "cellSelected")
let businessProfilePicture = String(user?["profPicString"] as! String)
if (businessProfilePicture.count) > 0 {
let url = URL(string: (businessProfilePicture))
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
}
} else {
let image = UIImage(named: "default")?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
}
} else {
print("Location services are not enabled")
}
} else if cell.isSelected == false {
var user = self.usersArray[indexPath.row]
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
print("No access")
cell.businessName.text = String(user?["businessName"] as! String)
cell.businessStreet.text = String(user?["businessStreet"] as! String)
cell.businessCity.text = String(user?["businessCity"] as! String)
//cell.selectedCell.image = UIImage(named: "cellNotSelected")
let businessProfilePicture = String(user?["profPicString"] as! String)
if (businessProfilePicture.count) > 0 {
let url = URL(string: (businessProfilePicture))
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
}
} else {
let image = UIImage(named: "default")?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
//cell.profileImage.image =
//cell.businessDistance.text = String(user?["distanceLabelText"] as! String)
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
print("%called")
cell.businessName.text = String(user?["businessName"] as! String)
cell.businessStreet.text = String(user?["businessStreet"] as! String)
cell.businessCity.text = String(user?["businessCity"] as! String)
cell.businessDistance.text = String(user?["distanceLabelText"] as! String)
//cell.selectedCell.image = UIImage(named: "cellNotSelected")
let businessProfilePicture = String(user?["profPicString"] as! String)
if (businessProfilePicture.count) > 0 {
let url = URL(string: (businessProfilePicture))
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
}
} else {
let image = UIImage(named: "default")?.potter_circle
cell.businessImage.contentMode = UIView.ContentMode.scaleAspectFill
cell.businessImage.image = image
}
}
} else {
print("Location services are not enabled")
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomerAddSelectedBusinessesCell
let user = usersArray[indexPath.row]
let name = user!["uid"] as? String
var NSdata = NSDictionary()
var realArray = [NSDictionary]()
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
if cell.accessoryType == .checkmark{
cell.accessoryType = .none
print("Deleted \(name!)")
if let idx = data.index(of:name!) {
data.remove(at: idx)
print(data)
}
} else {
cell.accessoryType = .checkmark
data.append(name!)
print(data)
}
}
print(data)
}
Selected Cells after view loads
Cells that appear to selected but are not
It's most common cell reusable issue. Cells which are visible to screen will be reused when you scroll.
Eg. You've 5 cells visible. when you select 2,3 and scroll down 7,8 will be selected.
To avoid this you've 2 options.
You can use external Bool array to manage this(Bool array count must be same as your array count).
You can put bool variable in your user dictionary to manage that.
So whenever your scroll, newly visible cell will not selected automatically.
My app is crashing after I do a leadswipe action. and then try to do a trail swipe. I get this error "Exception: "Actions view not added to view hierarchy after calling -swipeActionController:insertActionsView:forItemAtIndexPath: on <UITableView: 0x7fa80b859400; frame = (20 390; 335 150); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000035c1080>; layer = <CALayer: 0x600003bd59e0>; contentOffset: {0, 0}; contentSize: {335, 0}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <SlideInTransition.HomeViewController: 0x7fa80b508070>>""
I have two tableviews, and two array one to each tableview.
here is my code for the contextual actions.
// Leading Swipe Action
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
if tableView == homeTableView {
let task = todaysTasks[indexPath.row]
let complete = markCompleteToday(at: indexPath)
if (task.value(forKey: "isComplete") as! Bool == false){
return UISwipeActionsConfiguration(actions: [complete])
}
} else if tableView == tomorrowDeadlinesTV{
let task = tomorrowTasks[indexPath.row]
let complete = markCompleteTomorrow(at: indexPath)
if (task.value(forKey: "isComplete") as! Bool == false){
return UISwipeActionsConfiguration(actions: [complete])
}
}
return UISwipeActionsConfiguration(actions: [])
}
// Mark as Completed Task
func markCompleteToday(at: IndexPath) -> UIContextualAction {
let df = DateFormatter()
df.dateFormat = "dd-MM-yyyy" // assigning the date format
let now = df.string(from: Date()) // extracting the date with the given format
let cell = homeTableView.cellForRow(at: at) as! CustomCell
let task = todaysTasks[at.row]
let completeActionImage = UIImage(named: "AddTask")?.withTintColor(.white)
let action = UIContextualAction(style: .normal, title: "Complete") { (action, view, completion) in
task.setValue(!(task.value(forKey: "isComplete") as! Bool), forKey: "isComplete")
self.homeTableView.cellForRow(at: at)?.backgroundColor = task.value(forKey: "isComplete") as! Bool ? Colors.greencomplete : .white
cell.backgroundColor = Colors.greencomplete
cell.labelsToWhite()
task.setValue("Finished " + now, forKey: "date")
do {
try
context.save()
} catch {
print("Markcomplete save error")
}
// cell.displayIcons(task: task)
completion(true)
}
//action.image = #imageLiteral(resourceName: "AddTask")
action.image = completeActionImage
action.backgroundColor = Colors.greencomplete
return action
}
func markCompleteTomorrow(at: IndexPath) -> UIContextualAction {
let df = DateFormatter()
df.dateFormat = "dd-MM-yyyy" // assigning the date format
let now = df.string(from: Date()) // extracting the date with the given format
let cell = tomorrowDeadlinesTV.cellForRow(at: at) as! CustomCell
let task = tomorrowTasks[at.row]
let completeActionImage = UIImage(named: "AddTask")?.withTintColor(.white)
let action = UIContextualAction(style: .normal, title: "Complete") { (action, view, completion) in
task.setValue(!(task.value(forKey: "isComplete") as! Bool), forKey: "isComplete")
self.tomorrowDeadlinesTV.cellForRow(at: at)?.backgroundColor = task.value(forKey: "isComplete") as! Bool ? Colors.greencomplete : .white
cell.backgroundColor = Colors.greencomplete
cell.labelsToWhite()
task.setValue("Finished " + now, forKey: "date")
do {
try
context.save()
} catch {
print("Markcomplete save error")
}
// cell.displayIcons(task: task)
completion(true)
}
action.image = completeActionImage
action.backgroundColor = Colors.greencomplete
return action
}
// Trailing Swipe Actions
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
if tableView == homeTableView {
let task = todaysTasks[indexPath.row]
let important = changeTextAction(at: indexPath, table: self.homeTableView, arr: todaysTasks)
let delete = deleteActionToday(at: indexPath)
if task.value(forKey: "isComplete") as? Bool == true {
return UISwipeActionsConfiguration(actions: [delete])
} else {
return UISwipeActionsConfiguration(actions: [delete,important])
}
} else if tableView == tomorrowDeadlinesTV {
let task = tomorrowTasks[indexPath.row]
let important = changeTextAction(at: indexPath, table: self.tomorrowDeadlinesTV, arr: tomorrowTasks)
let delete = deleteActionTomorrow(at: indexPath)
if task.value(forKey: "isComplete") as? Bool == true {
return UISwipeActionsConfiguration(actions: [delete])
} else {
return UISwipeActionsConfiguration(actions: [delete,important])
}
} else {
return UISwipeActionsConfiguration(actions: [])
}
}
// Mark as Important Action
// notification toggle
func toggleTaskReminder(at: IndexPath) -> UIContextualAction{
// let reminderImage = UIImage(named: "")
let action = UIContextualAction(style: .normal, title: "Notification") { (action, view, completion) in
}
return action
}
func changeTextAction(at: IndexPath , table:UITableView, arr:[NSManagedObject]) -> UIContextualAction{
let editActionImage = UIImage(named: "Edit")?.withTintColor(.white)
let action = UIContextualAction(style: .normal, title: "Important") { (action, view, completion) in
self.changeTaskText(at: at, tableview: table, arr: arr)
completion(true)
}
action.image = editActionImage
action.backgroundColor = .purple
return action
}
func deleteActionToday(at: IndexPath) -> UIContextualAction {
let deleteActionImage = UIImage(named: "Delete")?.withTintColor(.white)
let action = UIContextualAction(style: .destructive , title: "Delete") { (action, view, completion) in
let objectToDelete = todaysTasks.remove(at: at.row)
context.delete(objectToDelete)
self.homeTableView.deleteRows(at: [at], with: .automatic)
do {
try
context.save()
} catch {
print("Problem while saving")
}
completion(true)
}
action.image = deleteActionImage
action.backgroundColor = Colors.reddelete
self.loadTasks(sort: .none)
homeTableView.reloadData()
tomorrowDeadlinesTV.reloadData()
return action
}
func deleteActionTomorrow(at: IndexPath) -> UIContextualAction {
let deleteActionImage = UIImage(named: "Delete")?.withTintColor(.white)
let action = UIContextualAction(style: .destructive , title: "Delete") { (action, view, completion) in
let objectToDelete = tomorrowTasks.remove(at: at.row)
context.delete(objectToDelete)
self.tomorrowDeadlinesTV.deleteRows(at: [at], with: .automatic)
do {
try
context.save()
} catch {
print("Problem while saving")
}
completion(true)
}
action.image = deleteActionImage
action.backgroundColor = Colors.reddelete
self.loadTasks(sort: .none)
homeTableView.reloadData()
tomorrowDeadlinesTV.reloadData()
return action
}
func loadTasks(sort: [NSSortDescriptor]?){
tomorrowTasks.removeAll()
todaysTasks.removeAll()
let tomorrow = DateComponents(year: currentDateComponents.year, month: currentDateComponents.month, day: currentDateComponents.day! + 1)
let dateTomorrow = Calendar.current.date(from: tomorrow)!
let df = DateFormatter()
df.dateFormat = "dd-MM-yyyy" // assigning the date format
let now = df.string(from: Date())
let tomorrowDateString = df.string(from: dateTomorrow)
print("TOMORROW'S DATE IS : " , tomorrowDateString)
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
if let sort = sort{
request.sortDescriptors = sort
}
do {
let allTasks = try context.fetch(request) as! [NSManagedObject]
for item in allTasks {
if item.value(forKey: "date") as! String == now {
todaysTasks.append(item)
}
if item.value(forKey: "date") as! String == tomorrowDateString {
tomorrowTasks.append(item)
}
}
print("loadTasks() fired!")
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
func changeTaskText(at:IndexPath , tableview: UITableView , arr:[NSManagedObject]){
let cell = tableview.cellForRow(at: at) as! CustomCell
let task = arr[at.row]
let alert = UIAlertController(title: "Edit Task", message: nil, preferredStyle: .alert)
alert.addTextField()
let submitAction = UIAlertAction(title: "Submit", style: .default) { (UIAlertAction) in
task.setValue(alert.textFields![0].text!, forKey: "name")
cell.setTask(task: task)
do {
try context.save()
self.allTaskViewController.tableView.reloadData()
} catch {
print("Edit Fail")
}
print(task)
}
alert.addAction(submitAction)
present(alert,animated: true)
}
initial view of view control where we start the download Once the download started it look like this I were working in a mp3 application where I need to download the mp3 from the list shown when the user click the download button it shows the control for pause, stop and a progress view. my download is working but I could not show the progress for the download all over the application. For example: if I am downloading some "x.mp3" song if the user click x.mp3 in VC1 the progress should also shown in VC3. Then the big problem I am facing is after downloading the download control should hide. Due to tableview reload function I cannot handle the UIrefresh which should show the download control and also the download button accordingly.
I have attached both my ViewController class the cell class and also the design
I have attached the "CELLFORROWATINDEXPATH" function for your reference and I have enclosed all the download functionality in the cell class.
viewcontroller Cellforrowatindexpath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let table_cell = tableView.dequeueReusableCell(withIdentifier: "LecturesVcCellID", for: indexPath) as! LecturesVcCell
let trackdata = searchResults[indexPath.row]
constantsList.playTypeArray.boolArray.append(false)
table_cell.configure(track: trackdata, index: indexPath.row)
table_cell.downloadbtn.tag = indexPath.row
table_cell.pauseButton.tag = indexPath.row
table_cell.cancelButton.tag = indexPath.row
table_cell.progressLabel.tag = indexPath.row
let x : Int = trackdata.classNumber as! Int
let className = String(x)
if index == indexPath.row {
table_cell.img_play.image=#imageLiteral(resourceName: "playing_track")
table_cell.lbl_SubTitle.text = "Now playing"
table_cell.lbl_SubTitle.textColor = #colorLiteral(red: 0.968627451, green: 0.7490196078, blue: 0.4823529412, alpha: 1)
}else {
table_cell.img_play.image=#imageLiteral(resourceName: "play_track")
table_cell.lbl_SubTitle.text = String(format: "Class - %# | %#", className,(trackdata.timeoftrack!))
table_cell.lbl_SubTitle.textColor = UIColor.lightGray
}
str_image = "https://www.imaginetventures.name/swamiji/wp-content/uploads/2019/01/introved.png"
constantsList.playTypeArray.imageLecture = str_image
table_cell.img_Show.sd_setImage(with: URL(string: str_image), placeholderImage: UIImage(named: "placeholder.png"))
table_cell.navigationController = self.navigationController
if str_Type .isEqual(to: "Playlist") {
table_cell.downloadbtn.isHidden = true
// table_cell.playlistAdditionImageview.isHidden = false
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
table_cell.playlistAdditionImageview.isHidden = true
}else {
table_cell.playlistAdditionImageview.isHidden = false
}
}else {
table_cell.downloadbtn.setImage(UIImage(named: "DATA11"), for: .normal)
// table_cell.playlistAdditionImageview.isHidden = true
}
table_cell.selectionStyle = .none
return table_cell
}
viewcontrollercell classfile:
override func awakeFromNib() {
super.awakeFromNib()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext
entity = NSEntityDescription.entity(forEntityName: "DownloadList", in: context)
}
#IBAction func pauseBtnAction (_ sender: UIButton) {
if(downloadTask != nil && isDownload) {
downloadTask!.cancel(byProducingResumeData: { (resumeData) -> Void in
self.downloadData = resumeData
})
isDownload = false
downloadTask = nil
pauseButton.setImage(UIImage(named: "start-button"), for: .normal)
}else
if(!isDownload && downloadData != nil) {
if let resumeData = self.downloadData {
downloadTask = urlSession?.downloadTask(withResumeData: resumeData)
} else {
let url:URL = URL(string: "\((constantsList.playTypeArray.arr_subCatagriesLecture.object(at: sender.tag) as AnyObject).value(forKey: "mp3") as! String)")!
downloadTask = downloadsSession?.downloadTask(with: url)
}
downloadTask!.resume()
isDownload = true
pauseButton.setImage(UIImage(named: "x"), for: .normal)
}
}
#IBAction func cancelBtnClicked (_ sender: UIButton) {
downloadTask?.cancel()
downloadData = nil
isDownload = false
downloadTask = nil
progress.progress = 0
progressLabel.text = "0%"
progress.setProgress(0.0, animated: true)
cancelButton.isHidden = true
pauseButton.isHidden = true
downloadbtn.isHidden = false
progressLabel.isHidden = true
progress.isHidden = true
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
#IBAction func downloadBtnAction(_ sender: UIButton) {
// sender.pulse()
self.reachability = Reachability.init()
if ((self.reachability?.connection) != .none) {
switch self.reachability?.connection {
case .wifi?:
print("Wifi")
print(sender.tag)
constantsList.playTypeArray.typeofnetwork = true
downloadTapped(sender: sender.tag)
case .cellular?:
print("mobile data")
print(sender.tag)
let alert = UIAlertController(title: "Mobile Data usage Alert!", message: "Downloads will be using mobile data!!", preferredStyle: UIAlertController.Style.alert);
let action1 = UIAlertAction(title: "Ok", style: .default) { (action:UIAlertAction) in
self.downloadTapped(sender: sender.tag)
}
let action2 = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
print("Cancelled")
}
alert.addAction(action1)
alert.addAction(action2)
self.navigationController?.present(alert, animated: true, completion: nil)
case .none:
print("Network Not reachable")
case .some(.none):
print("Some")
}
}else {
print("No Internet")
}
}
func downloadTapped(sender : Int) {
constantsList.playTypeArray.arrayDownloadSort.append(sender as NSNumber)
print(constantsList.playTypeArray.arrayDownloadSort)
constantsList.playTypeArray.boolArray[sender] = true
showDownloadControls = true
downloadbtn.isHidden = true
pauseButton.isHidden = false
cancelButton.isHidden = false
progressLabel.isHidden = true
progress.isHidden = false
progressLabel.text = "Downloading..."
var urlConfiguration:URLSessionConfiguration!
urlConfiguration = URLSessionConfiguration.default
let queue:OperationQueue = OperationQueue.main
urlSession = URLSession.init(configuration: urlConfiguration, delegate: self, delegateQueue: queue)
let url:NSURL = NSURL(string: "\((constantsList.playTypeArray.arr_subCatagriesLecture.object(at: sender) as AnyObject).value(forKey: "mp3") as! String)")!
downloadTask = urlSession.downloadTask(with: url as URL)
downloadTask.resume()
}
//DOWNLOADING THE FILE UPDATE THE PROGRESS
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 2
let progress1 = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite);
// 3
print(progress1)
// 4
// OperationQueue.main.addOperation
DispatchQueue.main.async (execute: {
self.isDownload = true
// 2
constantsList.playTypeArray.isDownloading = true
self.progress.setProgress(progress1, animated: false)
self.progressLabel.text = String(progress1 * 100) + "%"
constantsList.playTypeArray.setprogress = progress1
let i = 0
if progress1 == 1.0 {
constantsList.playTypeArray.isDownloading = false
print(i + 1)
}
})
}
//AFTER DOWNLOADING SAVE THE FILE TO APP DATA
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Finish Downloading")
let filemanager = FileManager()
let directoryURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0]
print(directoryURL)
let docDirectoryURL = NSURL(fileURLWithPath: "\(directoryURL)")
print(docDirectoryURL)
let destinationFileName = downloadTask.originalRequest?.url?.lastPathComponent
print(destinationFileName!)
let destinationURL = docDirectoryURL.appendingPathComponent("\(destinationFileName!)")
print(destinationURL!)
if let path = destinationURL?.path {
if filemanager.fileExists(atPath: path) {
do {
try filemanager.removeItem(at: destinationURL!)
}catch let error as NSError{
print(error.localizedDescription)
}
}
}
do {
try filemanager.copyItem(at: location, to: destinationURL!)
print(destinationURL!)
print(downloadbtn.tag)
newUser = NSManagedObject(entity: entity!, insertInto: context)
dic66 = constantsList.playTypeArray.arr_subCatagriesLecture.object(at: downloadbtn.tag) as? NSDictionary
newUser.setValue(dic66?.value(forKey: "classname") as! NSNumber, forKey: "classname")
newUser.setValue("\(String(describing: dic66?.value(forKey: "time") as! String))", forKey: "time")
newUser.setValue("\(String(describing: dic66?.value(forKey: "title") as! String))", forKey: "songtitle")
newUser.setValue("\(destinationURL!.path)", forKey: "mp3Url")
newUser.setValue("\(String(describing: constantsList.playTypeArray.imageLecture!))", forKey: "imageurl")
do {
try context.save()
print(context)
self.cancelButton.isHidden = true
self.pauseButton.isHidden = true
self.downloadbtn.isHidden = true
self.progressLabel.isHidden = true
self.progress.isHidden = true
print("Successful")
} catch {
print("failed")
}
}catch {
print("Error while copying file")
}
downloadData = nil;
}
func configure(track: Track,index: Int) {
var filefound: Bool = false
var shopwControls: Bool = true
if(constantsList.playTypeArray.boolArray[index]) {
filefound = true
shopwControls = false
self.progress.setProgress(constantsList.playTypeArray.setprogress, animated: true)
}
lbl_Title.text = track.songTitle
//CLASS AND VOLUME UPDATION
let x : Int = track.classNumber as! Int
let className = String(x)
lbl_SubTitle.text = String(format: "Class - %# | %#", className,(track.timeoftrack!))
//core Data checking for already downloaded file
request.returnsObjectsAsFaults = false
request.resultType = .dictionaryResultType
do {
let result = try context.fetch(request)
dic66 = constantsList.playTypeArray.arr_subCatagriesLecture.object(at: index) as? NSDictionary
for data in result as! [[String:Any]] {
print(data["classname"] as! Int)
print(dic66?.value(forKey: "classname") as! Int)
if data["classname"] as! Int == (dic66?.value(forKey: "classname") as! Int) {
filefound = true
}
}
} catch {
print("Failed")
}
// If the track is already downloaded, enable cell selection and hide the Download button
selectionStyle = filefound ? UITableViewCell.SelectionStyle.gray : UITableViewCell.SelectionStyle.none
downloadbtn.isHidden = filefound
pauseButton.isHidden = shopwControls
cancelButton.isHidden = shopwControls
progress.isHidden = shopwControls
}
}
I'm having a problem regarding a feature where You can delete a cell and so delete and event using an Alamofire JSON request.
When I swipe the cell and click delete, the app crashes, but the event get deleted successfully and with no errors, in facts on Laravel side I get the event deleted.
I tried everything, but I really can't figure out how to fix the crash.
Can someone help me please?
here is my .Swift code:
import UIKit
import Alamofire
class EventViewController: UITableViewController {
#objc var transition = ElasticTransition()
#objc let lgr = UIScreenEdgePanGestureRecognizer()
#objc let rgr = UIScreenEdgePanGestureRecognizer()
let rc = UIRefreshControl()
#IBOutlet weak var myTableView: UITableView!
var myTableViewDataSource = [NewInfo]()
let url = URL(string: "http://ns7records.com/staffapp/api/events/index")
override func viewDidLoad() {
super.viewDidLoad()
loadList()
// Add Refresh Control to Table View
if #available(iOS 10.0, *) {
tableView.refreshControl = rc
} else {
tableView.addSubview(rc)
}
// Configure Refresh Control
rc.addTarget(self, action: #selector(refreshTableData(_:)), for: .valueChanged)
let attributesRefresh = [kCTForegroundColorAttributeName: UIColor.white]
rc.attributedTitle = NSAttributedString(string: "Caricamento ...", attributes: attributesRefresh as [NSAttributedStringKey : Any])
DispatchQueue.main.async {
}
// MENU Core
// customization
transition.sticky = true
transition.showShadow = true
transition.panThreshold = 0.3
transition.transformType = .translateMid
// menu// gesture recognizer
lgr.addTarget(self, action: #selector(MyProfileViewController.handlePan(_:)))
rgr.addTarget(self, action: #selector(MyProfileViewController.handleRightPan(_:)))
lgr.edges = .left
rgr.edges = .right
view.addGestureRecognizer(lgr)
view.addGestureRecognizer(rgr)
}
#objc private func refreshTableData(_ sender: Any) {
// Fetch Table Data
//myTableViewDataSource.removeAll()
tableView.reloadData()
loadList()
}
func loadList(){
var myNews = NewInfo()
// URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
//
// })
let task = URLSession.shared.dataTask(with:url!) {
(data, response, error) in
if error != nil
{
print("ERROR HERE..")
}else
{
do
{
if let content = data
{
let myJson = try JSONSerialization.jsonObject(with: content, options: .mutableContainers)
//print(myJson)
if let jsonData = myJson as? [String : Any]
{
if let myResults = jsonData["data"] as? [[String : Any]]
{
//dump(myResults)
for value in myResults
{
if let myTitle = value["title"] as? String
{
//print(myTitle)
myNews.displayTitle = myTitle
}
if let myLocation = value["local"] as? String
{
myNews.location = myLocation
}
if let myDate = value["date"] as? String
{
myNews.date = myDate
}
if let myDescription = value["description"] as? String
{
myNews.description = myDescription
}
if let myCost = value["cost"] as? String
{
myNews.cost = myCost
}
if let myNumMembers = value["num_members"] as? String
{
myNews.num_members = myNumMembers
}
if let myNumMembers_conf = value["num_members_confirmed"] as? String
{
myNews.num_members_confirmed = myNumMembers_conf
}
if let myStartEvent = value["time_start"] as? String
{
myNews.startEvent = myStartEvent
}
if let myEndEvent = value["time_end"] as? String
{
myNews.endEvent = myEndEvent
}
if let myId = value["id"] as? Int
{
myNews.idEvent = myId
}
//x img
// if let myMultimedia = value["data"] as? [String : Any]
// {
if let mySrc = value["event_photo"] as? String
{
myNews.event_photo = mySrc
print(mySrc)
}
self.myTableViewDataSource.append(myNews)
}//end loop
dump(self.myTableViewDataSource)
DispatchQueue.main.async
{
self.tableView.reloadData()
self.rc.endRefreshing()
}
}
}
}
}
catch{
}
}
}
task.resume()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)->CGFloat {
return 150
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myTableViewDataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath)
let myImageView = myCell.viewWithTag(11) as! UIImageView
let myTitleLabel = myCell.viewWithTag(12) as! UILabel
let myLocation = myCell.viewWithTag(13) as! UILabel
let DateLabelCell = myCell.viewWithTag(14) as! UILabel
let numMembLabel = myCell.viewWithTag(15) as! UILabel
let numMembConfLabel = myCell.viewWithTag(16) as! UILabel
myTitleLabel.text = myTableViewDataSource[indexPath.row].displayTitle
myLocation.text = myTableViewDataSource[indexPath.row].location
DateLabelCell.text = myTableViewDataSource[indexPath.row].date
numMembLabel.text = myTableViewDataSource[indexPath.row].num_members
numMembConfLabel.text = myTableViewDataSource[indexPath.row].num_members_confirmed
if let imageURLString = myTableViewDataSource[indexPath.row].event_photo,
let imageURL = URL(string: AppConfig.public_server + imageURLString) {
myImageView.af_setImage(withURL: imageURL)
}
return myCell
}
//per passare da un viewcontroller a detailviewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? EventDetailViewController {
destination.model = myTableViewDataSource[(tableView.indexPathForSelectedRow?.row)!]
// Effetto onda
let vc = segue.destination
vc.transitioningDelegate = transition
vc.modalPresentationStyle = .custom
}
//menu
if let vc = segue.destination as? MenuViewController{
vc.transitioningDelegate = transition
vc.modalPresentationStyle = .custom
//endmenu
}
}
//menu slide
#objc func handlePan(_ pan:UIPanGestureRecognizer){
if pan.state == .began{
transition.edge = .left
transition.startInteractiveTransition(self, segueIdentifier: "menu", gestureRecognizer: pan)
}else{
_ = transition.updateInteractiveTransition(gestureRecognizer: pan)
}
}
//endmenuslide
////ximg
func loadImage(url: String, to imageView: UIImageView)
{
let url = URL(string: url )
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else
{
return
}
DispatchQueue.main.async
{
imageView.image = UIImage(data: data)
}
}.resume()
}
/// star to: (x eliminare row e x muove row)
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObjTemp = myTableViewDataSource[sourceIndexPath.item]
myTableViewDataSource.remove(at: sourceIndexPath.item)
myTableViewDataSource.insert(movedObjTemp, at: destinationIndexPath.item)
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete){
// print(parameters)
let idEvent = (myTableViewDataSource[indexPath.item].idEvent)
let parameters = [
// "id": UserDefaults.standard.object(forKey: "userid")! ,
"id" : idEvent,
] as [String : Any]
let url = "http://www.ns7records.com/staffapp/public/api/deleteevent"
print(url)
Alamofire.request(url, method:.post, parameters:parameters,encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success:
print(response)
let JSON = response.result.value as? [String : Any]
//self.myTableView.reloadData()
let alert = UIAlertController(title: "Yeah!", message: "Evento modificato con successo!", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.destructive, handler: nil))
self.present(alert, animated: true, completion: nil)
// let data = JSON! ["data"] as! NSDictionary
if let jsonData = JSON as? [String : Any]
{
print(jsonData)
self.myTableViewDataSource.remove(at : indexPath.item)
self.myTableView.deleteRows(at: [indexPath], with: .automatic)
let indexPath = IndexPath(item: 0, section: 0)
//self.myTableView.deleteRows(at: [indexPath], with: .fade)
//self.myTableView.reloadData()
// }
// }
//}
}
case .failure(let error):
print(error)
let alert = UIAlertController(title: "Aia", message: "Non puoi cancellare questo evento!", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.destructive, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
}
#IBAction func EditButtonTableView(_ sender: UIBarButtonItem) {
self.myTableView.isEditing = !self.myTableView.isEditing
sender.title = (self.myTableView.isEditing) ? "Done" : "Edit"
}
/// end to: (x eliminare row e x muove row)
}
// MARK: -
// MARK: UITableView Delegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}