Tableviews "cellForRowAt" is never called after adding real data - ios

My program is built the following:
I have a UIViewController SingleEventViewController to layout and handle data of an event. One of its elements is an UIView TeilnehmerTableView with an UITableView participantsTableView in it.
As the name says its there for displaying the users, participating at the Event. First I implemented it without "real" data. I hardcoded the users array and built the table view based on it. This worked perfectly.
But since I implemented the fetchUsers Method and filled the array with that information, the tableView does not appear at all. Obviously it doesn't display anything when I first load the page, because numberOfRowsInSection returns 0. But even after the fetch, when I reload the tableview, and the numberOfRowsInSection returns a value, the tableview is empty. While debugging I noticed that the method for populating the cells cellForRowAt never gets called. I have no clue what's going on since it all worked before using real data.
Can someone tell me what's going on?
import UIKit
import Firebase
class TeilnehmerTableView: UIView, UITableViewDelegate, UITableViewDataSource {
var parentVC: SingleEventViewController?
var users = [User]()
let participantsTableView: UITableView = {
let ctv = UITableView()
return ctv
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.green
setupTableView()
setupViews()
confBounds()
fetchUsers()
}
func setupTableView() {
participantsTableView.delegate = self
participantsTableView.dataSource = self
participantsTableView.register(TeilnehmerTVCell.self, forCellReuseIdentifier: TeilnehmerTVCell.reuseIdentifier)
participantsTableView.tintColor = .white
}
func setupViews() {
addSubview(participantsTableView)
}
func confBounds(){
participantsTableView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Users: ",users.count)
return users.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
print("heightForRowAt Called")
return 44
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRowAt Called")
let row = tableView.dequeueReusableCell(withIdentifier: TeilnehmerTVCell.reuseIdentifier, for: indexPath) as! TeilnehmerTVCell
row.user = users[indexPath.item]
return row
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
//MARK: - Methods
func fetchUsers() {
print("Fetching users..")
let ref = Database.database().reference().child("users")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any] else { return }
dictionaries.forEach({ (key, value) in
if key == Auth.auth().currentUser?.uid {
print("Found myself, omit from list")
return
}
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.users.append(user)
})
self.users.sort(by: { (u1, u2) -> Bool in
return u1.username.compare(u2.username) == .orderedAscending
})
DispatchQueue.main.async( execute: {
self.participantsTableView.reloadData()
})
}) { (err) in
print("Failed to fetch users for search:", err)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the code for the parent the SingleEventVC:
import UIKit
import MapKit
class SingleEventViewController: UIViewController {
var thisEvent: Event
var eventName: String?
var eventDescription: String?
var eventLocation: CLLocationCoordinate2D?
var eventStartingDate: Date?
var eventFinishingDate: Date?
var eventNeedsApplication: Bool?
let padding: CGFloat = 20
//MARK: - GUI Objects
let scrollView: UIScrollView = {
let view = UIScrollView()
return view
}()
let teilnehmerLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 20)
label.text = "Teilnehmer"
label.textColor = .black
return label
}()
let teilnehmerTV: TeilnehmerTableView = {
let tvt = TeilnehmerTableView()
tvt.backgroundColor = .brown
return tvt
}()
let buttonDividerView: UIView = {
let tdv = UIView()
tdv.backgroundColor = UIColor.gray
return tdv
}()
let participateButton: UIButton = {
let button = UIButton()
button.backgroundColor = CalendarSettings.Colors.buttonBG
button.setTitle("Teilnehmen", for: .normal)
button.setTitleColor(CalendarSettings.Colors.darkRed, for: .normal)
return button
}()
init(event: Event) {
thisEvent = event
super.init(nibName: nil, bundle: nil)
setupDefaultValues()
}
override func viewDidLoad() {
super.viewDidLoad()
applyDefaultValues()
setupNavBar()
setupViews()
confBounds()
getSnapshotForLocation()
}
//MARK: - Setup
func setupDefaultValues() {
eventName = thisEvent.eventName
eventDescription = thisEvent.eventDescription
eventLocation = thisEvent.eventLocation
eventStartingDate = thisEvent.eventStartingDate
eventFinishingDate = thisEvent.eventFinishingDate
eventNeedsApplication = thisEvent.eventNeedsApplication
}
func applyDefaultValues() {
titleLabel.text = eventName
descLabel.text = eventDescription
if let start = eventStartingDate, let finish = eventFinishingDate {
timeLabel.text = "Von \(start.getHourAndMinuteAsStringFromDate()) bis \(finish.getHourAndMinuteAsStringFromDate())"
}
if let location = eventLocation {
locationLabel.text = getStringFromLocation(location: location)
mapLabel.text = getStringFromLocation(location: location)
}
if let date = eventStartingDate {
dateLabel.text = formatDate(date: date)
}
}
func setupNavBar() {
self.navigationItem.title = "Event"
}
func setupViews() {
view.addSubview(scrollView)
view.addSubview(participateButton)
participateButton.addTarget(self, action: #selector (buttonClick), for: .touchUpInside)
view.addSubview(buttonDividerView)
scrollView.addSubview(teilnehmerLabel)
scrollView.addSubview(teilnehmerTV)
teilnehmerTV.parentVC = self
}
func confBounds(){
let tabbarHeight = self.tabBarController?.tabBar.frame.height ?? 0
let tableviewHeight: CGFloat = CGFloat(teilnehmerTV.users.count) * 44
participateButton.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: tabbarHeight, paddingRight: 0, width: 0, height: 50)
buttonDividerView.anchor(top: nil, left: view.leftAnchor, bottom: participateButton.topAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
scrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: buttonDividerView.topAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
teilnehmerLabel.anchor(top: descLabel.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 20, paddingLeft: padding, paddingBottom: 0, paddingRight: padding, width: 0, height: 0)
teilnehmerTV.anchor(top: teilnehmerLabel.bottomAnchor, left: view.leftAnchor, bottom: scrollView.bottomAnchor, right: view.rightAnchor, paddingTop: 5, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: tableviewHeight)
}
override func viewDidLayoutSubviews() {
let objHeight = titleLabel.frame.height + locationLabel.frame.height + dateLabel.frame.height + timeLabel.frame.height + mapView.frame.height + notizenLabel.frame.height + descLabel.frame.height + teilnehmerLabel.frame.height + teilnehmerTV.frame.height
let paddingHeight = 10+0+50+padding+20+5 - 15
print(objHeight, paddingHeight)
scrollView.contentSize = CGSize(width: view.frame.width, height: objHeight+paddingHeight)
}
//MARK: - Methods
#objc func buttonClick() {
teilnehmerTV.participantsTableView.reloadData()
print(teilnehmerTV.participantsTableView.numberOfRows(inSection: 0))
scrollView.reloadInputViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

It's because you have not added TeilnehmerTableView to the view in SingleEventViewController when you make the fetch request - you make the request during initialization of TeilnehmerTableView. Try calling fetchUsers from SingleEventViewController after you have added TeilnehmerTableView to the view; it should then work.

Related

How to put separate different string values into multiple lines in a UITableView

I have UITableView and within it, I am attempting to display two separate string values, each on a different line. The second string or the amount should be displayed under the first line of text within the table view.
The code below displays how the text within the table view is constrained and other functions where I assume the function of separating the values into different lines will be issues:
func set(bio: Bio){
bioLabel.text = bio.statistics
amountLabel.text = bio.amount
}
func configureBioLabel(){
bioLabel.numberOfLines = 0
bioLabel.textColor = .systemGreen
bioLabel.font = UIFont(name: "Seravek", size: 22)
bioLabel.adjustsFontSizeToFitWidth = true
}
func setBioLabelConstraints(){
bioLabel.translatesAutoresizingMaskIntoConstraints = false
bioLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
bioLabel.anchor(top: nil, leading: leadingAnchor, bottom: nil, trailing: nil, padding: .init(top: 0, left: 50, bottom: 0, right: 0))
bioLabel.heightAnchor.constraint(equalToConstant: 80).isActive = true
bioLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 16/9).isActive = true
}
func configureAmount(){
amountLabel.numberOfLines = 0
amountLabel.textColor = .systemYellow
amountLabel.font = UIFont(name: "Seravek", size: 22)
amountLabel.adjustsFontSizeToFitWidth = true
}
func setAmountLabelConstriants(){
amountLabel.translatesAutoresizingMaskIntoConstraints = false
amountLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
amountLabel.anchor(top: nil, leading: leadingAnchor, bottom: nil, trailing: nil, padding: .init(top: 0, left: 50, bottom: 0, right: 0))
amountLabel.heightAnchor.constraint(equalToConstant: 80).isActive = true
amountLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
}
Then in this extension, the separate sting values are added to the table view.
extension BioInfo: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return bio.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: BioCellId.bioCellId) as! CustomBioCell
let bios = bio[indexPath.row]
cell.set(bio: bios)
return cell
}
}
struct Bio{
var statistics: String
var amount: String
}
extension BioInfo{
func fetchData() -> [Bio]{
let info = Bio(statistics: "Text", amount: "0")
let info2 = Bio(statistics: "Text", amount: "0")
let info3 = Bio(statistics: "Text", amount: "0")
return [info, info2, info3]
}
}
My table view cells are defined here:
class BioInfo: UICollectionViewCell{
lazy var tableView: UITableView = {
let tblView = UITableView()
tblView.delegate = self
tblView.dataSource = self
tblView.translatesAutoresizingMaskIntoConstraints = false
return tblView
}()
var bio: [Bio] = []
struct BioCellId {
static let bioCellId = "CustomBioCell"
}
override init(frame: CGRect) {
super.init(frame: frame)
bio = fetchData()
setupTableView()
}
func setupTableView() {
addSubview(tableView)
translatesAutoresizingMaskIntoConstraints = false
tableView.anchor(top: topAnchor, leading: leadingAnchor, bottom: nil, trailing: trailingAnchor, padding: .init(top: 350, left: 0, bottom: 300, right: 0),size: .init(width: frame.width, height: 300))
tableView.rowHeight = 100
tableView.register(CustomBioCell.self, forCellReuseIdentifier: BioCellId.bioCellId)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

CollectionView cells not appearing correctly using mmPlayerView

I have a collectionview that contains a video, a caption, profile image and a few other labels in each cell. When I load the collection view the first cell loads perfectly. Everything is all showing in the correct position. The rest however is loaded incorrectly for example sometimes the video does not fit properly within the imageview so it looks like its been cut out or sometimes the caption doesn't show at all.
But when I scroll down more a cell might randomly show everything in the correct positions again. I am using a library MMPlayerView to load my videos into the cell. I have been trying to figure out why the first cell loads perfectly but then the rest doesn't?
I am trying to create a collectionview that just plays videos like Instagram. I've also build out the collectionview programmatically if someone needs to know.
viewController
lazy var mmPlayerLayer: MMPlayerLayer = {
let l = MMPlayerLayer()
l.cacheType = .memory(count: 5)
l.coverFitType = .fitToPlayerView
l.videoGravity = AVLayerVideoGravityResizeAspectFill
l.replace(cover: CoverA.instantiateFromNib())
return l
}()
#IBOutlet weak var newsfeedCollectionView: UICollectionView!
var videoPosts = [videoPost]()
override func viewDidLoad() {
super.viewDidLoad()
newsfeedCollectionView?.register(videoListCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
newsfeedCollectionView.addObserver(self, forKeyPath: "contentOffset", options: [.new], context: nil)
newsfeedCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 20, right:0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.updateByContentOffset()
self.startLoading()
}
fetchAllPosts()
}
fileprivate func landscapeAction() {
// just landscape when last result was finish
if self.newsfeedCollectionView.isDragging || self.newsfeedCollectionView.isTracking || self.presentedViewController != nil {
return
}
if UIDevice.current.orientation.isLandscape {
let full = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FullScreenViewController") as! FullScreenViewController
MMLandscapeWindow.shared.makeKey(root: full, playLayer: self.mmPlayerLayer, completed: {
print("landscape completed")
})
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentOffset" {
self.updateByContentOffset()
NSObject.cancelPreviousPerformRequests(withTarget: self)
self.perform(#selector(startLoading), with: nil, afterDelay: 0.3)
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
func fetchAllPosts() {
//posts are loaded from firebase
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return videoPosts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! videoListCollectionViewCell
let postModelInfo = videoPosts[indexPath.item]
cell.post = postModelInfo
return cell
}
fileprivate func updateByContentOffset() {
let p = CGPoint(x: newsfeedCollectionView.frame.width/2, y: newsfeedCollectionView.contentOffset.y + newsfeedCollectionView.frame.width/2)
if let path = newsfeedCollectionView.indexPathForItem(at: p),
self.presentedViewController == nil {
self.updateCell(at: path)
}
}
fileprivate func updateCell(at indexPath: IndexPath) {
if let cell = newsfeedCollectionView.cellForItem(at: indexPath) as? videoListCollectionViewCell {
cell.delegate = self
cell.videoDelegate = self
// this thumb use when transition start and your video dosent start
mmPlayerLayer.thumbImageView.image = cell.photoImageView.image
// set video where to play
if !MMLandscapeWindow.shared.isKeyWindow {
mmPlayerLayer.playView = cell.photoImageView
}
// set url prepare to load
mmPlayerLayer.set(url: URL(string:(cell.post?.videoUrl)!), state: { (status) in
switch status {
case .failed(let err):
let alert = UIAlertController(title: "error", message: err.description, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
case .ready:
print("Ready to Play")
case .playing:
print("Playing")
case .pause:
print("Pause")
case .end:
print("End")
default: break
}
})
cell.layoutIfNeeded()
}
}
#objc fileprivate func startLoading() {
if self.presentedViewController != nil {
return
}
// start loading video
mmPlayerLayer.startLoading()
self.landscapeAction()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let post = videoPosts[indexPath.item]
let width = (view.frame.width)
let imageWidth = post.imageWidth?.floatValue,
imageHeight = post.imageHeight?.floatValue
let oldHeight = CGFloat(imageWidth! / imageHeight!)
let height = CGFloat(width / oldHeight + 138)
let rect = NSString(string: post.postCaption!).boundingRect(with: CGSize(width:view.frame.width, height:1000), options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin), attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 14)], context: nil)
return CGSize(width:view.frame.width, height:rect.height + height)
}
CollectionViewCell
protocol mmPlayerDelegate{
func startLoading(for cell: videoListCollectionViewCell)
}
class videoListCollectionViewCell: UICollectionViewCell {
var delegate: PostCellDelegate?
var videoDelegate: videoDelegate?
var mmPlayerDelegate: mmPlayerDelegate?
var post: videoPost? {
didSet {
guard let postImageUrl = post?.imageUrl else { return }
self.photoImageView.sd_setImage(with: URL(string: postImageUrl), placeholderImage: UIImage(named: "blurLogo"))
self.userProfileImageView.sd_setImage(with: URL(string: post?.profilePic), placeholderImage: UIImage(named: "blurLogo"))
usernameLabel.text = post?.fullName
captionLabel.text = post!.postCaption
}
override func prepareForReuse() {
super.prepareForReuse()
post = nil
}
let userProfileImageView: CustomImageView = {
var iv = CustomImageView()
iv.image = UIImage(named:"blurLogo")
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.backgroundColor = UIColor.darkGray
iv.clipsToBounds = true
return iv
}()
var photoImageView: CustomImageView = {
var iv = CustomImageView()
iv.image = UIImage(named:"blurLogo")
iv.contentMode = .scaleAspectFill
iv.backgroundColor = UIColor.clear
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = true
iv.layer.masksToBounds = true
return iv
}()
let usernameLabel: UILabel = {
let label = UILabel()
label.text = ""
label.textColor = UIColor.white
label.font = UIFont.boldSystemFont(ofSize: 14)
return label
}()
let postDate: UILabel = {
let label = UILabel()
label.text = ""
label.font = UIFont.boldSystemFont(ofSize: 10)
label.textColor = UIColor.gray
return label
}()
let likeCount: UILabel = {
let label = UILabel()
label.text = "0"
label.textColor = UIColor.white
label.font = UIFont.systemFont(ofSize: 14)
return label
}()
let optionsButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("•••", for: .normal)
button.setTitleColor(.white, for: .normal)
return button
}()
lazy var likeButton: UIButton = {
let button = UIButton(type: .system)
let stencil = imageLiteral(resourceName: "like_unselected").withRenderingMode(.alwaysTemplate)
button.setImage(stencil, for: .normal)
button.tintColor = UIColor.white
button.addTarget(self, action: #selector(handleLike), for: .touchUpInside)
return button
}()
lazy var commentButton: UIButton = {
let button = UIButton(type: .system)
let stencil = imageLiteral(resourceName: "comment").withRenderingMode(.alwaysTemplate)
button.setImage(stencil, for: .normal)
button.tintColor = UIColor.white
button.addTarget(self, action: #selector(handleComment), for: .touchUpInside)
return button
}()
let captionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = UIColor.white
label.font = UIFont.systemFont(ofSize: 14)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(userProfileImageView)
addSubview(usernameLabel)
addSubview(optionsButton)
addSubview(photoImageView)
userProfileImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 40, height: 40)
userProfileImageView.layer.cornerRadius = 40 / 2
usernameLabel.anchor(top: topAnchor, left: userProfileImageView.rightAnchor, bottom: photoImageView.topAnchor, right: optionsButton.leftAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
photoImageView.anchor(top: userProfileImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 16, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addSubview(postDate)
postDate.anchor(top: topAnchor, left: nil, bottom: photoImageView.topAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 8, width: 0, height: 0)
optionsButton.anchor(top: photoImageView.bottomAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 15, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 44, height: 0)
addSubview(fullScreen)
fullScreen.anchor(top: nil, left: nil , bottom: photoImageView.bottomAnchor, right: photoImageView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 15, paddingRight: 15, width: 30, height: 30)
addSubview(activityIndicatorView)
activityIndicatorView.anchor(top: photoImageView.centerYAnchor, left: photoImageView.centerXAnchor , bottom: nil, right: nil, paddingTop: -15, paddingLeft: -15, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupActionButtons()
addSubview(likeCountButton)
likeCountButton.anchor(top: photoImageView.bottomAnchor, left: likeButton.rightAnchor, bottom: nil, right: nil, paddingTop: 16, paddingLeft: -18, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addSubview(captionLabel)
captionLabel.anchor(top: likeButton.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 6, paddingRight: 8, width: 0, height: 0)
addSubview(viewCount)
viewCount.anchor(top: captionLabel.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 10, paddingLeft: 8, paddingBottom: 6, paddingRight: 8, width: 0, height: 0)
}
fileprivate func setupActionButtons() {
let stackView = UIStackView(arrangedSubviews: [likeButton, likeCount,commentButton, commentCount])
stackView.distribution = .fillProportionally
addSubview(stackView)
stackView.anchor(top: photoImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 190, height: 50)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Couple of things which might need changing in your code to achieve what you need:
Dont use sizeForItemAt to derive the height of custom cell.
Instead use estimatedItemSize in your viewDidLoad.
Compute the height for the cell depending on the content for the cell in didSet function for post.

Swift reload collection view with button click not works

I am new to swift. I used LTBA components to build swift app. I follow some tutorial to develop social application like twitter. I start implement to follow button functions. its call the the another API call but same response come with minor modification(e.g - number of follower property increase by one). call also perfectly working. but collection view did not reload.my code is.
ViewConroller
import LBTAComponents
import TRON
import SwiftyJSON
class HomeDatasourceController: DatasourceController {
let errorMessageLabel: UILabel = {
let label = UILabel()
label.text = "Apologies something went wrong. Please try again later..."
label.textAlignment = .center
label.numberOfLines = 0
label.isHidden = true
return label
}()
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
collectionViewLayout.invalidateLayout()
}
func follow(){
print("inside controller")
Service.sharedInstance.fetchfollowHomeFeed { (homeDatasource, err) in
if let err = err {
self.errorMessageLabel.isHidden = false
if let apiError = err as? APIError<Service.JSONError> {
if apiError.response?.statusCode != 200 {
self.errorMessageLabel.text = "Status code was not 200"
}
}
return
}
self.datasource = homeDatasource
self.collectionView?.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(errorMessageLabel)
errorMessageLabel.fillSuperview() //LBTA method call
collectionView?.backgroundColor = UIColor(r: 232, g: 236, b: 241)
setupNavigationBarItems()
Service.sharedInstance.fetchHomeFeed { (homeDatasource, err) in
if let err = err {
self.errorMessageLabel.isHidden = false
if let apiError = err as? APIError<Service.JSONError> {
if apiError.response?.statusCode != 200 {
self.errorMessageLabel.text = "Status code was not 200"
}
}
return
}
self.datasource = homeDatasource
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//first section of users
if indexPath.section == 0 {
guard let user = self.datasource?.item(indexPath) as? User else { return .zero }
let estimatedHeight = estimatedHeightForText(user.bioText)
return CGSize(width: view.frame.width, height: estimatedHeight + 66)
} else if indexPath.section == 1 {
//our tweets size estimation
guard let tweet = datasource?.item(indexPath) as? Tweet else { return .zero }
let estimatedHeight = estimatedHeightForText(tweet.message)
return CGSize(width: view.frame.width, height: estimatedHeight + 74)
}
return CGSize(width: view.frame.width, height: 200)
}
private func estimatedHeightForText(_ text: String) -> CGFloat {
let approximateWidthOfBioTextView = view.frame.width - 12 - 50 - 12 - 2
let size = CGSize(width: approximateWidthOfBioTextView, height: 1000)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
let estimatedFrame = NSString(string: text).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
return estimatedFrame.height
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 1 {
return .zero
}
return CGSize(width: view.frame.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
if section == 1 {
return .zero
}
return CGSize(width: view.frame.width, height: 64)
}
}
DataCell
import LBTAComponents
class UserCell: DatasourceCell {
override var datasourceItem: Any? {
didSet {
guard let user = datasourceItem as? User else { return }
followButton.addTarget(self, action: #selector(follow), for: .touchUpInside)
nameLabel.text = user.name
usernameLabel.text = user.username
bioTextView.text = user.bioText
profileImageView.loadImage(urlString: user.profileImageUrl)
}
}
let profileImageView: CachedImageView = {
let imageView = CachedImageView()
imageView.image = #imageLiteral(resourceName: "profile_image")
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
return imageView
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Brian Voong"
label.font = UIFont.boldSystemFont(ofSize: 16)
return label
}()
let usernameLabel: UILabel = {
let label = UILabel()
label.text = "#buildthatapp"
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = UIColor(r: 130, g: 130, b: 130)
return label
}()
let bioTextView: UITextView = {
let textView = UITextView()
textView.text = "iPhone, iPad, iOS Programming Community. Join us to learn Swift, Objective-C and build iOS apps!"
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .clear
return textView
}()
let followButton: UIButton = {
let button = UIButton()
button.layer.cornerRadius = 5
button.layer.borderColor = twitterBlue.cgColor
button.layer.borderWidth = 1
button.setTitle("Follow", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
button.setTitleColor(twitterBlue, for: .normal)
button.setImage(#imageLiteral(resourceName: "follow"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -8, bottom: 0, right: 0)
// button.titleEdgeInsets = UIEdgeInsets
return button
}()
func follow(){
print("inside source")
var link = HomeDatasourceController()
link.follow()
}
override func setupViews() {
super.setupViews()
backgroundColor = .white
separatorLineView.isHidden = false
separatorLineView.backgroundColor = UIColor(r: 230, g: 230, b: 230)
addSubview(profileImageView)
addSubview(nameLabel)
addSubview(usernameLabel)
addSubview(bioTextView)
addSubview(followButton)
profileImageView.anchor(self.topAnchor, left: self.leftAnchor, bottom: nil, right: nil, topConstant: 12, leftConstant: 12, bottomConstant: 0, rightConstant: 0, widthConstant: 50, heightConstant: 50)
nameLabel.anchor(profileImageView.topAnchor, left: profileImageView.rightAnchor, bottom: nil, right: followButton.leftAnchor, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 12, widthConstant: 0, heightConstant: 20)
usernameLabel.anchor(nameLabel.bottomAnchor, left: nameLabel.leftAnchor, bottom: nil, right: nameLabel.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 20)
bioTextView.anchor(usernameLabel.bottomAnchor, left: usernameLabel.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: -4, leftConstant: -4, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
followButton.anchor(topAnchor, left: nil, bottom: nil, right: self.rightAnchor, topConstant: 12, leftConstant: 0, bottomConstant: 0, rightConstant: 12, widthConstant: 120, heightConstant: 34)
}
}
It using LBTA components.i tried self.collectionView?.reloadData() but its not reloaded. Please help me to fix this problem.Please help me to figure out this problem
Note that you are using a closure in the follow() method so you need to write the code for reloading, in the main queue.
write your reload code in follow like this
DispatchQueue.main.async{
self.collectionView.reloadData()
}
Try to add a completion handler for the follow method:
func follow(completionHandler: #escaping (Any, NSError?) -> ()){
Service.sharedInstance.fetchfollowHomeFeed { (homeDatasource, err) in
do {
// handle the response from the api call
completionHandler(homeDatasource, nil)
} catch let err{
self.errorMessageLabel.isHidden = false
if let apiError = err as? APIError<Service.JSONError> {
if apiError.response?.statusCode != 200 {
self.errorMessageLabel.text = "Status code was not 200"
}
}
}
}
}
And call your method like:
self.follow() {homeDatasource,error in
self.collectionView.reloadData()
}

Uncaught Exception in Button

I have a uiview with a couple of elements that I use for a comment function in my app. There is a text field, button, and line separator. Everything renders fine however when I click submit the app crashes and I get this error.
'NSInvalidArgumentException', reason: '-[UIButton copyWithZone:]: unrecognized selector sent to instance 0x7fe58c459620'
I don't see anything wrong with my implementation so this error is a little confusing to me. This is the class for my UIView
import UIKit
protocol CommentInputAccessoryViewDelegate {
func handleSubmit(for comment: String?)
}
class CommentInputAccessoryView: UIView, UITextFieldDelegate {
var delegate: CommentInputAccessoryViewDelegate?
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
fileprivate let submitButton: UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
//submitButton.isEnabled = false
return submitButton
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
// backgroundColor = .red
addSubview(submitButton)
submitButton.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right:rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
addSubview(commentTextField)
commentTextField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupLineSeparatorView()
}
fileprivate func setupLineSeparatorView(){
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
addSubview(lineSeparatorView)
lineSeparatorView.anchor(top:topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
}
#objc func handleSubmit(for comment: String?){
guard let commentText = commentTextField.text else{
return
}
delegate?.handleSubmit(for: commentText)
}
#objc func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func clearCommentTextField(){
commentTextField.text = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the accompanying class that ultimately handles the submission through a protocol method
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: CommentInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let commentInputAccessoryView = CommentInputAccessoryView(frame:frame)
commentInputAccessoryView.delegate = self
return commentInputAccessoryView
}()
#objc func handleSubmit(for comment: String?){
guard let comment = comment, comment.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will clear the comment text field
self.containerView.clearCommentTextField()
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
The associated method for the target/action #selector(handleSubmit) must be
#objc func handleSubmit(_ sender: UIButton) { ...
or
#objc func handleSubmit() { ...
Other forms are not supported.
Does the code compile at all?
Actually you can't use self in the initializer let submitButton: UIButton = { .. }()
The problem seems to be that UIButton doesn't have a copyWithZone method and that you can't define delegates for UIButtons:
what are the delegate methods available with uibutton

how to edit tableViewCell when Tableview is in editing mode

I'm making a App like whatsApp Please check whatsApp calls tab. When we click we click on edit how I should move cell towards left when tableview is in editing mode. Please ignore the Ancher function I created a separate function to handle anchoring . ScreenShot 1 ScreenShot 2
import UIKit
class CallsTabController: UITableViewController {
let id = "reuseIdentifier"
var arr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(cell.self, forCellReuseIdentifier: id)
arr = ["Sachin","Papa","Mummy","Sachin","Papa","Mummy","Sachin","Papa","Mummy"]
setupNav()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell1 = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)as! cell
cell1.data = arr[indexPath.row]
return cell1
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
arr.remove(at: indexPath.row)
print("Delete")
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath){
cell.separatorInset = UIEdgeInsetsMake(0, 30, 0, 0)
tableView.separatorInset = UIEdgeInsetsMake(0, 30, 0, 0)
}
//
func add() {
}
func EditAction() {
tableView.isEditing = !tableView.isEditing
if tableView.isEditing == true {
self.navigationItem.leftBarButtonItem?.title = "done"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "clear", style: .plain, target: self, action:#selector(self.EditAction))
}
else {
self.navigationItem.leftBarButtonItem?.title = "Edit"
let button1 = UIBarButtonItem(image: #imageLiteral(resourceName: "Calls"), style: .plain, target: self, action: #selector(self.add))
self.navigationItem.rightBarButtonItem = button1
}
}
func setupNav(){
let button1 = UIBarButtonItem(image: #imageLiteral(resourceName: "Calls"), style: .plain, target: self, action: #selector(self.add))
self.navigationItem.rightBarButtonItem = button1
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action:#selector(self.EditAction))
self.navigationItem.titleView = {
let search = UISegmentedControl(items: ["All","Missed"])
search.apportionsSegmentWidthsByContent = false
search.selectedSegmentIndex = 0
return search
}()
self.tableView.addSubview(UISearchBar())
}
}
class cell : UITableViewCell {
let frameview = { () -> UIView in
let view = UIView()
return view
}()
var Name = { () -> UILabel in
let lab = UILabel()
lab.font = UIFont(name: "HelveticaNeue-Light", size: 16.0)
lab.textColor = UIColor.black
return lab
}()
var Description = { () -> UILabel in
let lab = UILabel()
lab.font = UIFont(name: "helveticaNeue-UltraLight", size: 12.0)
lab.textColor = UIColor.black
return lab
}()
var TimeLabel = { () -> UILabel in
let lab = UILabel()
lab.textAlignment = .right
lab.font = UIFont(name: "helveticaNeue-light", size: 12.0)
lab.textColor = UIColor.gray
lab.text = "yesterday"
return lab
}()
var infoBtn:UIButton = { () -> UIButton in
let btn = UIButton()
btn.setImage(#imageLiteral(resourceName: "info"), for: .normal)
return btn
}()
var Calltype:UIImageView = { () -> UIImageView in
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = #imageLiteral(resourceName: "Recivecall")
return imageView
}()
var data:String? {
didSet {
Name.text = self.data
Description.text = "home"
}
}
var frameLeftAncherValue:CGFloat?{
didSet {
Ancher()
}
}
var frameRightAncherValue = 0
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
Setup()
Ancher()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func Setup() {
addSubview(frameview)
addSubview(Calltype)
addSubview(Name)
addSubview(Description)
addSubview(infoBtn)
addSubview(TimeLabel)
}
func Ancher() {
Calltype.anchor(frameview.topAnchor, left: frameview.leftAnchor, bottom: frameview.bottomAnchor, right: nil, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 0, widthConstant: 20.0, heightConstant: 0)
infoBtn.anchor(nil, left: nil, bottom: nil, right: rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 16.0, widthConstant: 20.0, heightConstant: 20.0)
infoBtn.anchorCenterYToSuperview()
Description.anchor(nil, left: Calltype.rightAnchor, bottom: frameview.bottomAnchor, right: infoBtn.leftAnchor, topConstant: 1, leftConstant: 4, bottomConstant: 4, rightConstant: 4, widthConstant: 0, heightConstant: 0)
Name.anchor(frameview.topAnchor, left: Calltype.rightAnchor, bottom: nil, right: infoBtn.leftAnchor, topConstant: 2, leftConstant: 4, bottomConstant: 0, rightConstant: 4, widthConstant: 0, heightConstant: 0)
TimeLabel.anchor(nil, left: nil, bottom: nil, right: infoBtn.leftAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: 0)
TimeLabel.anchorCenterYToSuperview()
frameview.anchor(topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 0, leftConstant: frameLeftAncherValue ??
0.0, bottomConstant: 0, rightConstant: CGFloat(frameRightAncherValue), widthConstant: 0, heightConstant: 0)
}
}
Your image looks as if you have added sub views to the cells view property and not the contentView property. In order for the content to re-size during editing, you must add new UIViews to the contentView property.
Example:
func setupViews() {
self.contentView.addSubview(frameview)
self.contentView.addSubview(Calltype)
self.contentView.addSubview(Name)
self.contentView.addSubview(Description)
self.contentView.addSubview(infoBtn)
self.contentView.addSubview(TimeLabel)
}

Resources