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()
}
Related
I have an issue updating the with anchor of a constraints inside my collectionView cell. I have two views representing bars as percentages of e.g. the total amount of goals scored for the home and for the away team see the following picture for clarification.
When I look into the statistics the for the first time, everything works fine and I get the right print statements in my console (e.g. Width HomeCell: 139.0 and Width AwayCell: 27.0 for index 0). When I go back to my pitchViewController and add some more goals, I get an error and both bars disappear.
I already tried to call layoutIfNeeded() or setNeedsLayout() on both bar views. But it didn't worked so far.
Here is my console output, relevant code underneath:
Width HomeCell: 83.0
Width AwayCell: 83.0
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x604000287440 UIView:0x7f870f461d50.width == 1 (active)>",
"<NSLayoutConstraint:0x60c00009a810 UIView:0x7f870f461d50.width == 83 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60c00009a810 UIView:0x7f870f461d50.width == 83 (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
My Custom Cell
import UIKit
class GameStatisticCell: BaseCell {
let statisticTitleLabel: UILabel = {
let label = UILabel()
label.text = "Schüsse aufs Tor"
label.textColor = ColorCodes.darkGray
label.textAlignment = .center
label.font = UIFont(name: "HelveticaNeue-Medium", size: 12)
return label
}()
let homeTeamStatistic: UILabel = {
let label = UILabel()
label.text = String(12)
label.textColor = ColorCodes.darkGray
label.textAlignment = .right
label.font = UIFont(name: "HelveticaNeue-CondensedBold", size: 20)
return label
}()
let awayTeamStatistic: UILabel = {
let label = UILabel()
label.text = String(2)
label.textColor = ColorCodes.darkGray
label.textAlignment = .left
label.font = UIFont(name: "HelveticaNeue-CondensedBold", size: 20)
return label
}()
var homeTeamStatisticBar: UIView = {
let view = UIView()
view.backgroundColor = UIColor.lightGray
return view
}()
var awayTeamStatisticBar: UIView = {
let view = UIView()
view.backgroundColor = UIColor.darkGray
return view
}()
let barCenter = pitchWidth! / 2
var barWidthHome: CGFloat = 10.0
var barWidthAway: CGFloat = 10.0
var statistic: Statistic? {
didSet {
guard let statisticName = statistic?.name else { return }
guard let homeValue = statistic?.home else { return }
guard let awayValue = statistic?.away else { return }
guard let homeBar = statistic?.homeBar else { return }
guard let awayBar = statistic?.awayBar else { return }
statisticTitleLabel.text = statisticName
homeTeamStatistic.text = homeValue.description
awayTeamStatistic.text = awayValue.description
barWidthHome = homeBar
barWidthAway = awayBar
print("Width HomeCell: \(barWidthHome)")
print("Width AwayCell: \(barWidthAway)")
homeTeamStatisticBar.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: barCenter, width: barWidthHome, height: 16)
awayTeamStatisticBar.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 4, paddingLeft: barCenter, paddingBottom: 0, paddingRight: 0, width: barWidthAway, height: 16)
self.homeTeamStatisticBar.layoutIfNeeded()
self.awayTeamStatisticBar.layoutIfNeeded()
}
}
override func setupCell() {
super.setupCell()
self.setNeedsLayout()
backgroundColor = .white
addSubview(statisticTitleLabel)
addSubview(homeTeamStatistic)
addSubview(awayTeamStatistic)
addSubview(homeTeamStatisticBar)
addSubview(awayTeamStatisticBar)
statisticTitleLabel.anchor(top: nil, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 4, paddingRight: 0, width: 0, height: 0)
addConstraint(NSLayoutConstraint(item: statisticTitleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
homeTeamStatistic.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
awayTeamStatistic.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
self.translatesAutoresizingMaskIntoConstraints = false
if let top = top {
self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
self.widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
self.heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
}
My Statistics CollectionView
import UIKit
class GameStatistics: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let cellId = "cellId"
let sectionHeader = "sectionHeader"
let sectionFooter = "sectionFooter"
let cellHeight: CGFloat = 40
let headerHeight: CGFloat = 80
let footerHeight: CGFloat = 50
let cellSpacing: CGFloat = 0
var statistics = [Statistic(name: "Tore", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Goals
Statistic(name: "Schüsse aufs Tor", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Shots on Target
Statistic(name: "Schüsse neben das Tor", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Shots off Target
Statistic(name: "Freistöße", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Free Kicks
Statistic(name: "Eckbälle", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Corner Kicks
Statistic(name: "Fouls", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Fouls
Statistic(name: "Abseits / Mittellinie", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Offside / Centerline
Statistic(name: "Strafen", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0)] //Cautions
var barWidthHome: CGFloat = 1.0
var barWidthAway: CGFloat = 1.0
var statisticValueSum: Int = 1
var valueHomeTeam: Int = 1
var valueAwayTeam: Int = 1
func updateGoals() {
valueHomeTeam = UserDefaults.standard.integer(forKey: "homegoals")
valueAwayTeam = UserDefaults.standard.integer(forKey: "awaygoals")
statisticValueSum = valueHomeTeam + valueAwayTeam
barWidthHome = CGFloat((Int(pitchWidth! / 2) - 40) * valueHomeTeam / statisticValueSum)
barWidthAway = CGFloat((Int(pitchWidth! / 2) - 40) * valueAwayTeam / statisticValueSum)
statistics[0] = Statistic(name: "Tore", home: valueHomeTeam, away: valueAwayTeam, homeBar: barWidthHome, awayBar: barWidthAway)
}
func showStatistics() {
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
// Dynamic Height of Collection View
let value: CGFloat = CGFloat(statistics.count)
let height: CGFloat = value * cellHeight + (value - 1) * cellSpacing + headerHeight + footerHeight
let y = window.frame.height - height
blackView.frame = window.frame
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
blackView.alpha = 0
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}, completion: nil)
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return statistics.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GameStatisticCell
cell.statistic = statistics[indexPath.item]
cell.layoutIfNeeded()
//dump(statistics)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return cellSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return cellSpacing
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionHeader, for: indexPath)
return supplementaryView
case UICollectionElementKindSectionFooter:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionFooter, for: indexPath)
return supplementaryView
default:
fatalError("Unexpected element kind")
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: headerHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: footerHeight)
}
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(GameStatisticCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(GameStatisticHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: sectionHeader)
collectionView.register(GameStatisticFooter.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: sectionFooter)
}
}
You should not add new constraints as those will obviously conflict with already existing ones (different padding). You should rather hold a reference to the dynamic constraints created in the setupCell and only update them in the statistic didSet {}
Solved it with two helper functions where I can set the constraints and update them in my statistic didSet. Took some time, but finally the above comment took me on the right track. Thank you.
func updateHomeBar() {
homeWidth?.constant = barWidthHome
homeTeamStatisticBar.setNeedsLayout()
}
func homeBarConstraints() {
homeTeamStatisticBar.translatesAutoresizingMaskIntoConstraints = false
var homeConstraints: [NSLayoutConstraint] = [
homeTeamStatisticBar.topAnchor.constraint(equalTo: topAnchor, constant: 4),
homeTeamStatisticBar.rightAnchor.constraint(equalTo: rightAnchor, constant: -barCenter),
homeTeamStatisticBar.heightAnchor.constraint(equalToConstant: 16)]
homeWidth = homeTeamStatisticBar.widthAnchor.constraint(equalToConstant: barWidthHome)
homeConstraints.append(homeWidth!)
NSLayoutConstraint.activate(homeConstraints)
}
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.
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.
There was a big problem that I can not solve for a couple of days.
I have a UIViewController with UICollectionView in which there will be 3 cells with different content. Here is my class:
class PostView: AppViewAddMenu, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var homeController: FeedController?
var contentArray = [PostContentModel]()
var postInfo: PostModel?
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 3
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
cv.backgroundColor = .white
return cv
}()
override func setupViewController() {
collectionView.register(PostViewCell.self, forCellWithReuseIdentifier: "cellId")
view.addSubview(collectionView)
view.addConstraintsWithFormat("H:|[v0]|", views: collectionView)
view.addConstraintsWithFormat("V:|-60-[v0]|", views: collectionView)
fetchPost(id: postInfo?.id)
}
func fetchPost(id: Int?){
guard let ID = id else {
print("ID faild")
return
}
let url: String = "https://FTP.ru/wp-json/mobileApi/v1/post/\(ID)"
ApiService.sharedInstance.fetchContent(url: url, completion: { (Posts: [PostContentModel]) in
self.contentArray = Posts
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! PostViewCell
cell.contentArray = contentArray
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 1000)
}
}
The idea of the first cell is to take json from a site that looks like this: [switch, content]. From the switch value in UICollectionViewCell, different functions are called, which add an element in UICollectionViewCell via addSubview (). Here's the code:
class PostViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
anchor = self.topAnchor
}
var anchor: NSLayoutYAxisAnchor?
var contentArray: [PostContentModel]?{
didSet{
for number in contentArray! {
if(number.switcher == 1) {
anchor = addTextContent(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 2){
anchor = addImageContent(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 3){
anchor = addCaptionFirst(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 4){
anchor = addButtonLink(content: number.content!, link: number.link!, bottomAnchor: anchor!)
}
}
}
}
func addTextContent(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor{
let shortContentPost: UILabel = {
let label = UILabel()
label.text = content
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.regular)
return label
}()
addSubview(shortContentPost)
shortContentPost.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return shortContentPost.bottomAnchor
}
func addImageContent(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let image: CustomImageView = {
let image = CustomImageView()
image.image = UIImage(named: content)
image.contentMode = .scaleAspectFill
image.clipsToBounds = true
return image
}()
let imageURL = "https://brodude.ru/wp-content/uploads/" + content.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
image.loadImageUsingUrlString(urlString: imageURL)
addSubview(image)
image.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 250)
return image.bottomAnchor
}
func addCaptionFirst(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let Caption: UILabel = {
let lb = UILabel()
lb.text = content
lb.numberOfLines = 0
lb.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.semibold)
return lb
}()
addSubview(Caption)
Caption.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 30, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return Caption.bottomAnchor
}
func addButtonLink(content: String, link: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let button: LinkButton = {
let bt = LinkButton()
bt.LinkString = link
bt.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.semibold)
bt.setTitle(content, for: UIControlState.normal)
bt.sizeToFit()
bt.titleLabel?.numberOfLines = 0
bt.contentHorizontalAlignment = .left
bt.setTitleColor(UIColor(red: 0.07, green: 0.32, blue: 0.89, alpha: 1.0), for: UIControlState.normal)
bt.addTarget(self, action: #selector(linkOut), for: .touchUpInside)
return bt
}()
addSubview(button)
button.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 20, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return button.bottomAnchor
}
The algorithm works satisfactorily, but the problem begins when the cell is updated. When scrolling, with collectionView.reloadData (). New subview layered on the past, the text becomes fatter and clogged device memory. The process continues indefinitely until it gives an error.
Example of a problem:, ,
Sorry for my English.
First of all, instead of:
addSubview(shortContentPost)
use:
contentView.addSubview(shortContentPost)
Do this for all the subviews that you add. contentView is supposed to hold your content.
Implement this prepareForReuse in your cell:
override func prepareForReuse() {
super.prepareForReuse()
let subviews = contentView.subviews
for subview in subviews {
subview.removeFromSuperview()
}
}
This will clean your cell's content before reusing the cell.
Although I would STRONGLY recommend NOT to add/remove those subviews dynamically, because then you lose much of the reuse mechanism. I would add all the views (labels/imageViews) to the cell by default, and then use their isHidden property to hide/unhide them based on the content (e.g., if an image should be show, set the isHidden to true on all the others). This would be performance-wise better for reusing, because then the UI objects will not have to be recreated everytime a cell is reused, you would just reconfigure the content of the labels/imageViews.
Here is an odd one. I have two bar button items, one is cancel and one is done. The done button saves the text in the textview in the coreData and then pops the current view from the navigationController and goes to previous viewcontroller. The cancel button just goes to the previous controller. The whole thing works when the textview is the firstResponder() or the keyboard is up. When the keyboard resigns as the firstResponder, both buttons don't work. As in the just don't do anything. Done button doesn't save the text nor it takes me to previous viewcontroller.
Here is the code for both the buttons:
Done button:
#objc func handleDoneButton() {
print("hello")
if edittaskview.text.isEmpty == true
{
moContext.delete(editnotes!)
}
else
{
editnotes?.sNote = edittaskview.text
}
var error: NSError?
do {
// Save The object
try moContext.save()
print("SAVED")
} catch let error1 as NSError {
error = error1
}
_ = navigationController?.popViewController(animated: true)
}
Cancel button:
#objc func handleCancelButton()
{
_ = navigationController?.popViewController(animated: true)
}
Again, the code in both these functions work when the buttons are tapped only when the textview is the first responder otherwise they don't.
ViewController code:
import UIKit
import CoreData
class editViewController: UIViewController, UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout,
UITextViewDelegate, UIGestureRecognizerDelegate{
var editnotes: addednotes?
var prioritynumber: Int = 1
let moContext = (UIApplication.shared.delegate as!
AppDelegate).persistentContainer.viewContext
var colorArray = [prioritylevels]()
lazy var edittaskview: UITextView = {
let textview = UITextView()
textview.isScrollEnabled = false
textview.font = UIFont.systemFont(ofSize: 16)
textview.backgroundColor = .white
textview.delegate = self
return textview
}()
var clearview: UIView = {
let view = UIView()
view.backgroundColor = .clear
//view.isUserInteractionEnabled = false
return view
}()
let rightBarButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(handleDoneButton))
let leftBarButton = UIBarButtonItem(title: "Cancel", style: .done, target: self, action: #selector(handleCancelButton))
lazy var priorityCV: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
return cv
}()
let line: UIView = {
let line = UIView()
return line
}()
let reminderView: UIView = {
let reminder = UIView()
reminder.backgroundColor = .white
return reminder
}()
let reminderTitle: UILabel = {
let title = UILabel()
title.text = "Due Date"
title.textColor = .black
title.font = UIFont(name: "Avenir Next", size: 16)
title.font = UIFont.boldSystemFont(ofSize: 16)
return title
}()
let reminderMsg: UILabel = {
let title = UILabel()
title.text = "No Due Date Set"
title.textColor = .black
// title.font = UIFont(name: "Avenir Next", size: 12)
title.font = UIFont.systemFont(ofSize: 12)
return title
}()
let reminderAddBtn: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Set", for: .normal)
btn.setTitleColor(.black, for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 14)
btn.addTarget(self, action: #selector(handleSetReminderBtn), for: .touchUpInside)
return btn
}()
var textHeightConstraint: NSLayoutConstraint?
override func viewWillAppear(_ animated: Bool) {
adjustTextViewHeight()
}
let datePicker: UIDatePicker = UIDatePicker()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red:0.97, green:0.97, blue:0.97, alpha:1.0)
datePicker.addTarget(self, action: #selector(datePickerValueChanged(_:)), for: .valueChanged)
//ColorArray for PriorityCV
colorArray = [prioritylevels(color: UIColor(red:0.00, green:0.78, blue:0.73, alpha:1.0), name: " Low", prioritynumber: 1), prioritylevels(color: UIColor(red:0.00, green:0.78, blue:0.35, alpha:1.0), name: "Medium", prioritynumber: 2),prioritylevels(color: UIColor(red:0.88, green:0.63, blue:0.00, alpha:1.0), name: " High", prioritynumber: 3), prioritylevels(color: UIColor(red:0.96, green:0.28, blue:0.70, alpha:1.0), name: "Urgent", prioritynumber: 4)]
priorityCV.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cvid")
navigationItem.title = "Edit Task"
self.navigationItem.setHidesBackButton(true, animated: false)
self.navigationController?.navigationBar.tintColor = UIColor.white
navigationItem.rightBarButtonItem = rightBarButton
navigationItem.leftBarButtonItem = leftBarButton
view.addSubview(clearview)
clearview.addSubview(line)
clearview.addSubview(edittaskview)
clearview.addSubview(priorityCV)
clearview.addSubview(reminderView)
reminderView.addSubview(reminderTitle)
reminderView.addSubview(reminderMsg)
reminderView.addSubview(reminderAddBtn)
edittaskview.text = editnotes?.sNote
edittaskview.becomeFirstResponder() //taskview becomes first responder here when the view is loaded.
// view.addSubview(datePicker)
setupViews()
line.backgroundColor = editnotes?.sPriorityColor
let dismissbytap = UITapGestureRecognizer()
dismissbytap.addTarget(self, action: #selector(handleDismissByTap))
dismissbytap.delegate = self
clearview.addGestureRecognizer(dismissbytap) //a clear uiview is added behind all the subviews. this uiview is used to dismiss keyboard when tapped on it.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colorArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cvid", for: indexPath)
cell.backgroundColor = colorArray[indexPath.row].color
let prioritylabel = UILabel(frame: CGRect(x: view.frame.width / 20, y: 0, width: view.frame.width / 4, height: 30))
cell.addSubview(prioritylabel)
prioritylabel.text = colorArray[indexPath.row].name
prioritylabel.textColor = .white
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width / 4, height: 30)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
prioritynumber = colorArray[indexPath.row].prioritynumber
line.backgroundColor = colorArray[indexPath.row].color
editnotes?.sPriorityColor = colorArray[indexPath.row].color
editnotes?.sPriorityNumber = prioritynumber
print(prioritynumber)
}
func setupViews()
{
_ = edittaskview.anchor(line.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, topConstant: 2, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
self.edittaskview.contentInset = UIEdgeInsetsMake(2, 8, 4, 8)
self.textHeightConstraint = edittaskview.heightAnchor.constraint(equalToConstant: 80)
self.textHeightConstraint?.isActive = true
self.adjustTextViewHeight()
_ = clearview.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = priorityCV.anchor(edittaskview.bottomAnchor, left: clearview.leftAnchor, bottom: nil, right: clearview.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 30)
_ = line.anchor(clearview.topAnchor, left: clearview.leftAnchor, bottom: nil, right: clearview.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 4)
_ = reminderView.anchor(priorityCV.bottomAnchor, left: clearview.leftAnchor, bottom: nil, right: view.rightAnchor, topConstant: 30, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 60)
_ = reminderTitle.anchor(reminderView.topAnchor, left: reminderView.leftAnchor, bottom: nil, right: nil, topConstant: 8, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: 100, heightConstant: 20)
_ = reminderMsg.anchor(reminderTitle.bottomAnchor, left: reminderView.leftAnchor, bottom: nil, right: nil, topConstant: 4, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: 100, heightConstant: 24)
_ = reminderAddBtn.anchor(reminderView.topAnchor, left: nil, bottom: nil, right: reminderView.rightAnchor, topConstant: 20, leftConstant: 0, bottomConstant: 20, rightConstant: 32, widthConstant: 30, heightConstant: 20)
}
#objc func handleDoneButton() {
print("hello")
if edittaskview.text.isEmpty == true
{
moContext.delete(editnotes!)
}
else
{
editnotes?.sNote = edittaskview.text
}
var error: NSError?
do {
// Save The object
try moContext.save()
print("SAVED")
} catch let error1 as NSError {
error = error1
}
_ = navigationController?.popViewController(animated: true)
}
func textViewDidChange(_ textView: UITextView) {
self.adjustTextViewHeight()
}
func adjustTextViewHeight() {
let fixedWidth = edittaskview.frame.size.width
let newSize = edittaskview.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
self.textHeightConstraint?.constant = newSize.height + 15
self.view.layoutIfNeeded()
}
#objc func handleCancelButton()
{
_ = navigationController?.popViewController(animated: true)
}
#objc func datePickerValueChanged(_ sender: UIDatePicker) {
let componenets = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: sender.date)
if let day = componenets.day, let month = componenets.month, let year = componenets.year, let hour = componenets.hour, let minute = componenets.minute {
print("\(day) \(month) \(year) \(hour) \(minute)")
}
}
#objc func handleDismissByTap() {
edittaskview.resignFirstResponder() // Resigning textview as first responder
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
This does seem odd - and I don't know why the navigation bar buttons are only calling the functions when your text view is active, but...
One way around it is to move your right and left UIBarButtonItem declarations into viewDidLoad() instead of at the class level.
As an aside, you'll get a better animation and keyboard appearance if you move edittaskview.becomeFirstResponder() from viewDidLoad() to viewDidAppear().
I don't know how but instead of creating bar buttons seperately and then assigning them like so self.navigationBarItem.leftbarbutton = leftBarbutton
I decided to construct them using UIBarButtonItem constructor.
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .done, target: self, action: #selector(handleCancelButton))
It worked!
Pretty odd to be honest.
Something you could do as well is making the declaration of your UIBarButtonItem a lazy var.
It's getting instantiated with the class. If you make it a lazy var it will get created when you need to use it. If you want to keep it a let and at class level, just do:
override func viewDidLoad() {
super.viewDidLoad()
yourBarButtonItem.target = self
}
Either of these methods will keep the target intact. I was having this same issue.
It's still a weird issue and I think the compiler should yell at you for trying to use self before initialization. It does so when you put self() instead.