I have a Protocol inside my CellClass which is detect the function 'editCommentInFeed'. I have create an 'editButton' inside my Cell and define the handler with 'editCommentField' which is calling per Delegate the function and over give the 2 Parameters. This 2 Parameters are important I need this Informations from the cell to detect in future actions which will be provided.
For example I had tried to print something when the Button ist Pressed inside the Cell. But even this Statement is not execute.
//This is the Cell Class
protocol CommentFeedProtocol {
func editCommentInFeed(cell: CommentCell, comment: Comment)
}
class CommentCell: BaseCell {
var delegate: CommentFeedProtocol?
let editButton: UIButton = {
let editbttn = UIButton(type: UIButton.ButtonType.system)
editbttn.setImage(UIImage(named: "editButton"), for: UIControl.State.normal)
editbttn.addTarget(self, action: #selector(editCommentField), for: UIControl.Event.touchUpInside)
return editbttn
}()
override func setUpCell() {
super.setUpCell()
backgroundColor = UIColor.white
setUpCommentCell()
}
fileprivate func setUpCommentCell(){
addSubview(profileImageView)
addSubview(editButton)
addSubview(commentTextView)
addSubview(seperator)
profileImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 40, height: 40)
profileImageView.layer.cornerRadius = 40/2
editButton.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right: rightAnchor, paddingTop: 5, paddingLeft: 0, paddingBottom: 5, paddingRight: 10, width: 45, height: 0)
commentTextView.anchor(top: topAnchor, left: profileImageView.rightAnchor, bottom: bottomAnchor, right: editButton.leftAnchor, paddingTop: 4, paddingLeft: 4, paddingBottom: 4, paddingRight: 4, width: 0, height: 0)
seperator.anchor(top: bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 5, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 1.5)
}
/*Objecthandler*/
#objc func editCommentField(){
guard let comment = comment else { return}
delegate?.editCommentInFeed(cell: self, comment: comment)
}
// This is the CollectionViewController
// As you can see I am also add the Protocol to the class
class CommentsController: UICollectionViewController, UICollectionViewDelegateFlowLayout, CommentFeedProtocol {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Kommentare"
postButton.imageView?.alpha = 0.5
postButton.isEnabled = false
collectionView.keyboardDismissMode = .interactive
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: commentCell)
collectionView.alwaysBounceVertical = true
self.collectionView.backgroundColor = UIColor.white
fetchComments()
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: commentCell, for: indexPath) as! CommentCell
cell.comment = comments[indexPath.item]
cell.delegate = self
return cell
}
// Here is the Delegate Function
func editCommentInFeed(cell: CommentCell, comment: Comment) {
print("Button was Pressed")
}
You can see inside the example that I am Adding the Protocol to the class and also call the function inside the Class. I am not getting a Error Message. The Button is only light hovered when is clicked, but don't execute anything ?!
Seems the delegate is not set to your ViewController.
inside your cellForItemAt function, you need to set cell.delegate = self.
so when your cell calls delegate.editCommentInFeed it actually calls your viewController's editCommentInFeed implemented function.
You haven't set the delegate in cellForItemAt:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: commentCell, for: indexPath) as! CommentCell
cell.comment = comments[indexPath.item]
cell.delegate = self. // You forgot to add this line
return cell
}
you can read more about it here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html
You can also search for great tutorials about it.
Related
For the ChatLogController of my messaging feature, I'm having trouble getting the message bubble to automatically adjust its size based on its textView content size. It's showing only one line from the textview as the rest of the text is truncated. Here's how I've defined my textView and the message bubbleContainer:
private let textView: UITextView = {
let tv = UITextView()
tv.backgroundColor = .clear
tv.font = .systemFont(ofSize: 16)
tv.isScrollEnabled = false
tv.isEditable = false
tv.textColor = .white
return tv
}()
private let bubbleContainer: UIView = {
let view = UIView()
view.backgroundColor = .systemPurple
return view
}()
And here are the constraints:
addSubview(bubbleContainer)
bubbleContainer.layer.cornerRadius = 12
bubbleContainer.anchor(top: topAnchor, bottom: bottomAnchor)
bubbleContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 250).isActive = true
bubbleLeftAnchor = bubbleContainer.leftAnchor.constraint(equalTo: profileImageView.rightAnchor, constant: 12)
bubbleLeftAnchor.isActive = false
bubbleRightAnchor = bubbleContainer.rightAnchor.constraint(equalTo: rightAnchor, constant: -12)
bubbleRightAnchor.isActive = false
bubbleContainer.addSubview(textView)
textView.anchor(top: bubbleContainer.topAnchor, left: bubbleContainer.leftAnchor,
bottom: bubbleContainer.bottomAnchor, right: bubbleContainer.rightAnchor,
paddingTop: 4, paddingLeft: 12, paddingBottom: 4, paddingRight: 12)
The ChatLogController is a UICollectionViewController, and this is the way I've set up sizeForItemAt:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let estimatedSizeCell = MessageCell(frame: frame)
estimatedSizeCell.message = messages[indexPath.row]
estimatedSizeCell.layoutIfNeeded()
let targetSize = CGSize(width: view.frame.width, height: 1000)
let estimatedSize = estimatedSizeCell.systemLayoutSizeFitting(targetSize)
return .init(width: view.frame.width, height: estimatedSize.height)
}
The delegate is set to self but it seems as if the function is not even being called.
in the pic below, you can see how the messages are being truncated and the cell height is stuck to 50 pixels, where it should have been adjusted to display the rest of the message:
Inside cell take label with 0 number of lines which has following constraints:
Leading: Grater than 90 superview
All other with as usual
I'm using FSCalendar to create a calendar for my app but I can not get the Interactive Scope Gesture to work. I have downloaded the example project and read through the documentation but it still crashes with this error Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value on line calendarHeightConstraint.constant = 350. All I'm trying to achieve is the exact same as the documentation shows with the Interactive Scope Gesture but programmatically.
var calendarHeightConstraint: NSLayoutConstraint!
let calendarView: FSCalendar = {
let cl = FSCalendar()
cl.allowsMultipleSelection = false
return cl
}()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor(named: "backgroundColor")
cv.showsVerticalScrollIndicator = false
cv.showsHorizontalScrollIndicator = false
return cv
}()
fileprivate lazy var scopeGesture: UIPanGestureRecognizer = {
[unowned self] in
let panGesture = UIPanGestureRecognizer(target: self.calendarView, action: #selector(self.calendarView.handleScopeGesture(_:)))
panGesture.delegate = self
panGesture.minimumNumberOfTouches = 1
panGesture.maximumNumberOfTouches = 2
return panGesture
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(named: "backgroundColor")
collectionView.delegate = self
collectionView.dataSource = self
calendarView.delegate = self
calendarView.dataSource = self
calendarHeightConstraint.constant = 350
self.calendarView.select(Date())
self.view.addGestureRecognizer(self.scopeGesture)
self.calendarView.addGestureRecognizer(self.scopeGesture)
self.calendarView.scope = .week
setupLayout()
setupCollectionView()
}
func setupLayout() {
view.addSubview(calendarView)
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 350)
view.addSubview(collectionView)
collectionView.anchor(top: calendarView.bottomAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 15, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
func setupCollectionView() {
collectionView.register(CalendarCollectionCell.self, forCellWithReuseIdentifier: cellId)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CalendarCollectionCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 70)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 15
}
func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
self.calendarHeightConstraint.constant = bounds.height
self.view.layoutIfNeeded()
}
You don't init
var calendarHeightConstraint: NSLayoutConstraint!
or link it as an outlet hence it's nil , you need to link it here
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 350)
so you can play with it's constant as needed , so you can do
NSLayoutConstraint.activate([
// add left , right and top constraints
])
in addition to
calendarHeightConstraint = calendarView.heightAnchor.constraint(equalToConstant: 350)
calendarHeightConstraint.isActive = true
Don't try to use calendarHeightConstraint before the above 2 lines as this will give the nil crash
You can fix this by doing two things.
Remove the line calendarHeightConstraint.constant = 350 from viewDidLoad.
Initialize the calendarHeightConstraint in setupLayout.
Instead of:
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 350)
Use:
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 0)
calendarHeightConstraint = calendarView.heightAnchor.constraint(equalToConstant: 350)
calendarHeightConstraint.isActive = true
I already have a UIViewController made programmatically, and would like at the bottom to have a collection view with 4 cells, but I want to be able to swipe through different pages of the collection view to see different cells. I'm not sure where to begin with this wether to enable paging on the collection view and how that would work with setting up the cells, or to create a page controller and adding the collection view to that? There are a couple of ways that I have seen online already, but that don't really fit my needs.
I would like something as such:
Let me know if I can provide you with more information. I just created a basic page controller but am not sure how to achieve what I'm looking for.
Edit: I created a collection view and added the constraints to get the layout I want; however I'm not sure how to make it swipe like a page.
Here's the code for the collection view:
let friendsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(FriendsCell.self, forCellWithReuseIdentifier: "cell")
cv.backgroundColor = UIColor.blue
cv.layer.cornerRadius = 10
return cv
}()
view.addSubview(friendsCollectionView)
friendsCollectionView.anchor(top: separatorView.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 50, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 250, height: 250)
friendsCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 75, height: 75)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsets(top: 85, left: 10, bottom: 0, right: 0)
}
if section == 1 {
return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
}
if section == 2 {
return UIEdgeInsets(top: 85, left: 5, bottom: 0, right: 0)
}
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 { return 1 }
if section == 1 { return 2 }
if section == 2 { return 1 }
return 0
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
All you need would be a UICollectionViewController or as I am assuming you have, a UIViewController that conforms to the collectionView protocols. Your UIViewController with one UICollectionView is a good starting point. However the cell you insert needs to be different. First of all, the collectionView within the ViewController needs to have collectionView?.isPagingEnabled = true and its layouts scrollDirection set to .horizontal. Having done that you need to create your cells.
The cells you implement should be a subclass of UICollectionViewController and UICollectionViewDelegateFlowLayout. This collectionView's layout needs to have scrollDirection set to .vertical although I believe you are not scrolling in this view anyways. The number of cells in this collectionView will be 4. You then dequeue your cell of which you want to have 4 (the one with the head in white on blue).
The UICollectionViewController needs to then be dequeued as the cell for your first collectionView. Basically:
Your main ViewController has a collectionView that scrolls horizontally. This collectionView has a UICollectionViewController as cells and scrolls vertically.
It may sound very complicated but I have done it and it works smoothly. Let me know if you have any questions.
I Want to add distance between cells(Top and bottom) and distance between cells and table view's walls(left, right): I need to do it using by UIEdgeInset, not by adding headerView to cell:
// Inside UITableViewCell subclass
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
}
Swift 4.2
override func layoutSubviews() {
super.layoutSubviews()
//set the values for top,left,bottom,right margins
let margins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
contentView.frame = contentView.frame.inset(by: margins)
}
In cellforRowat populate data according to section like indexPath.section. and add below method
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: self.myTableView.frame.width, height: 10))
headerView.backgroundColor = UIColor.clear
return headerView
}
I am trying to implement a chat screen using UICollectionView and data is displayed as expected. However when I try to scroll it a few times my data gets distorted as explained in the screenshots. Can anyone suggest what's going wrong and how to solve it? Thanks!
First it shows:
After scrolling a few times it shows:
Code of all the methods related to UICollectionView I'm using:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let count = chatCategoriesArray.messages.count
if count != 0 {
return count
}
return 0
}
var allCellsHeight: CGFloat = 0.0
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatLogMessageCell
cell.messageTextView.text = chatCategoriesArray.messages[indexPath.item]
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatCategoriesArray.messages[indexPath.item]).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18)], context: nil)
if chatCategoriesArray.senderIds[indexPath.item] == "s_\(self.studentInstance.tutorIdFound)"{
cell.profileImageView.image = UIImage(named: "three.png")
cell.profileImageView.isHidden = false
cell.messageTextView.frame = CGRect(x: 48 + 8, y:0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x: 48, y: 0, width: estimatedFrame.width + 16 + 8, height: estimatedFrame.height + 20)
self.currentCellWidth = Double(estimatedFrame.width + 16 + 8)
cell.textBubbleView.backgroundColor = .white
cell.addSubview(cell.profileImageView)
cell.addConstraintsWithFormat(format: "H:|-8-[v0(30)]", views: cell.profileImageView)
cell.addConstraintsWithFormat(format: "V:[v0(30)]|", views: cell.profileImageView)
}
else{
cell.profileImageView.image = UIImage(named: "two.png")
cell.textBubbleView.backgroundColor = UIColor(r: 28, g:168, b:261)
cell.messageTextView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 46, y:0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.messageTextView.textColor = .white
cell.textBubbleView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 8 - 46, y: 0, width: estimatedFrame.width + 16 + 8, height: estimatedFrame.height + 20)
self.currentCellWidth = Double(estimatedFrame.width + 16 + 8)
cell.addSubview(cell.profileImageView)
cell.addConstraintsWithFormat(format: "H:[v0(30)]-8-|", views: cell.profileImageView)
cell.addConstraintsWithFormat(format: "V:[v0(30)]|", views: cell.profileImageView)
}
allCellsHeight += cell.frame.height
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if allCellsHeight < (collectionView.frame.height){
return UIEdgeInsets(top: view.frame.height - allCellsHeight, left: 0, bottom: 0, right: 0)
}
else {
return UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatCategoriesArray.messages[indexPath.item]).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
Collection cells are reused. That means that when you scroll up/down, the cells that become invisible are removed from hierarchy and queued for reusing. Then the collection view calls cellForItem: again for the items that become visible. dequeueReusableCell does not always create a new instance. Usually it will only return a cell that has become invisible for you to setup it again with new data.
If you add views/constraints during setup, you have to make sure to remove the ones you have added previously, otherwise the cell will have duplicate views and conflicting constraints.
Also note that allCellsHeight cannot work like this. cell.frame.height won't be correct immediately after setup (before actual layout) and since the method can be called several times for the same item, you cannot just add to a global variable. You should rather use collectionView.contentSize.height instead.
This is classic reusable cell trouble. This happens cause collection view reuse your receiver cells setting to create sender message box.
I will suggest you to use two different cell for sender and receiver. With constraint set on first load. This will have a positive impact on performance as well.
Check following image to understand how to use 2 cell.