I'm trying to create a calendar like as Google or iOS has. For calendar I'm using amazing FSCalendar. It work perfectly. But I have a problem with tableview for day events. I created uitableview with timeline (1 cell = 30 minutes). But how to add events to here that look like Google or iOS calendar, for example
I think I need to add buttons for each events and add it to uitableview.
This is my code:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (hour_end - hours_begin) * step
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.timeLabel.layer.zPosition = 1
cell.timeLabel.isHidden = true
if indexPath.row % step == 0 && indexPath.row != ((hour_end - hours_begin) * step) - 1 {
let hour = indexPath.row / step + hours_begin
cell.timeLabel.text = (hour < 10 ? "0": "") + String(hour) + ":00"
cell.timeLabel.layer.zPosition = 10
cell.timeLabel.isHidden = false
cell.separatorInset = UIEdgeInsets(top: 0, left: 50, bottom: 0, right: 0)
}
else {
cell.timeLabel.text = ""
cell.timeLabel.isHidden = true
cell.separatorInset = UIEdgeInsets(top: 0, left: 2000, bottom: 0, right: 0)
cell.timeLabel.layer.zPosition = 1
}
add_event(indexPath: indexPath, nRef: 5, offset: 0, height: 64.0)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
}
func add_event(indexPath: IndexPath, nRef: Int, offset: CGFloat, height: CGFloat)
{
let row = indexPath.row
let rectOfCellInTableViewCoordinates = tableView.rectForRow(at: indexPath)
let rectOfCellInSuperviewCoordinates = view.convert(rectOfCellInTableViewCoordinates, to: tableView.superview)
print("\(row) \(rectOfCellInSuperviewCoordinates.origin.x) \(rectOfCellInSuperviewCoordinates.origin.y)")
if row == nRef {
if let z = view.viewWithTag(nRef) as! UIButton?{
print("z found")
z.removeFromSuperview()
}
let btn_x = 50
let btn_width = tableView.frame.width - 50
let frame = CGRect(x: CGFloat(btn_x), y: rectOfCellInSuperviewCoordinates.origin.y + offset, width: btn_width, height: height)
let overlay_btn = UIButton(frame: frame)
overlay_btn.backgroundColor = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 0.3)
overlay_btn.setTitle("Press for more details", for: .normal)
overlay_btn.setTitleColor(UIColor.black, for: .normal)
overlay_btn.layer.cornerRadius = 8
overlay_btn.tag = 5
tableView.addSubview(overlay_btn)
}
}
But, I think it is not great solution. Any ideas?
I would recommend using a scroll view for what you are trying to accomplish. Instead of using cells and adding views... splitting them up between cells will be tricky. Instead, you can have your scroll view that has all of the dividers and hour marks.
Implement a custom UIView that will serve as an event. When the user creates an event, it will be a subview of your scrollview. Say for example your scrollview is 2400px long and each hour is 100px, If the event starts at 12pm, add the UIView to the 1200px mark...
That is a rough explanation of how I would approach this problem. Feel free to reach out if you have further questions.
Related
I have two tableviews inside my stack view. I am resizing them depending on the amount of data that is retrieved from Firestore. The issue I am facing is whilst the tableview is resize the top table view "ingredientsTV" shows all the data where as the "instructionsTV" only shows some of the data. My array.count displays the correct number of items in the array but them items are not getting displayed.
//Code for resize tableviews
override func viewWillLayoutSubviews() {
super.updateViewConstraints()
self.ingredientsTVHeight?.constant = self.ingredientsTV.contentSize.height
self.instructionsTVHeight.constant = self.instructionsTV.contentSize.height
self.ingredientsTV.contentInset = UIEdgeInsets(top: 0, left: -20, bottom: 0, right: 0)
self.instructionsTV.contentInset = UIEdgeInsets(top: 0, left: -20, bottom: 0, right: 0)
}
//setupview, called in viewdidload
//MARK: Functions
private func setupView() {
ingredientsTV.delegate = self
ingredientsTV.dataSource = self
instructionsTV.delegate = self
instructionsTV.dataSource = self
recipeImage.layer.cornerRadius = 5
recipeNameLbl.text = recipe.name
prepTimeLbl.text = recipe.prepTime
cookTimeLbl.text = recipe.cookTime
servesLabel.text = recipe.serves
if let url = URL(string: recipe.imageUrl) {
recipeImage.kf.setImage(with: url)
recipeImage.layer.cornerRadius = 5
}
}
//MARK: Tableview functions
extension PocketChefRecipeDetailsVC {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (tableView == self.ingredientsTV) {
return recipe.ingredients.count
}else {
return recipe.method.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (tableView == self.ingredientsTV) {
let cell = ingredientsTV.dequeueReusableCell(withIdentifier: "ingredientsCell", for: indexPath) as? ingredientsCell
cell?.ingredientsLbl.text = recipe.ingredients[indexPath.row]
return cell!
}else {
let cellB = instructionsTV.dequeueReusableCell(withIdentifier: "instructionsCell", for: indexPath) as? InstructionsCell
cellB?.instructionsLbl.text = recipe.method[indexPath.row]
return cellB!
}
}
}
*Recipe data is getting passed from previous view controller
I'm going to take a shot in the dark and say that maybe your stack view needs to be re laid out after you reload data.
Make sure to call
// after ingredientsTV.reloadData() and instructionsTV.reloadData() gets called
stackview.setNeedsLayout()
You can try adding a fixed height to each row and see if it has any populated data or not. Add this to your extension
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
Additionally, you can also set the background color of the cell in cellForRowAt() just to ensure the rows are visible.
Note: Do check if your stack view's constraints are set for all 4 sides.
Intro
Every time a user submits a comment, it is uploaded to the database. After that happened, the collectionView is reloaded. However, the reload does not have the expected behavior. Instead of adding the one comment to the end of the collectionView, it seems to randomly stack some other comment-cells and only display two comment cells, no matter how many comments there are.
This is the output of the View Debugger, you can clearly see that the collectionView cells are stacked:
At this point I would like to clarify that this is not a sizing error of the cells. They all have the correct size assigned, and if I call reloadData on the collectionView, at a later point in time, all the cells are displayed correctly.
I think things will become more clear if you take a look at the gif:
I really don't have any idea where this error comes from or how to fix it.
Code
Initialization of collectionView
private lazy var timelineCollectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.estimatedItemSize = CGSize(width: self.view.frame.width, height: 10)
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
cv.contentInsetAdjustmentBehavior = .never
cv.backgroundColor = .clear
cv.scrollIndicatorInsets = UIEdgeInsets(top: self.coloredTitleBarHeight - self.getStatusBarHeight(), left: 0, bottom: 0, right: 0)
cv.delegate = self
cv.dataSource = self
cv.register(TLContentCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "content-header-cell")
cv.register(TLContentCell.self, forCellWithReuseIdentifier: "comment-cell")
return cv
}()
Code of the UICollectionViewDelegate methods:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "content-header-cell", for: indexPath) as! TLContentCell
cell.isHeaderCell = true
cell.timelineContent = tlContentItem
cell.delegate = self
cell.isUserInteractionEnabled = true
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return commentItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "comment-cell", for: indexPath) as! TLContentCell
cell.delegate = self
cell.timelineContent = TLContent(nativeContentItem: commentItems[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let indexPath = IndexPath(row: 0, section: section)
let headerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: indexPath) as! TLContentCell
return headerView.contentView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height),
withHorizontalFittingPriority: .required, // Width is fixed
verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
}
The code of the comment-upload handler function:
#objc func submitComment() {
submitCommentButton.isEnabled = false
submitCommentButton.backgroundColor = ColorCodes.lightGray
guard let user = Auth.auth().currentUser else {
print("No user detected. Redirecting to the login screen!")
self.displayWelcomeScreen()
return
}
guard commentTextView.text.count > 0, let commentContent = commentTextView.text else { print("Submission of comment aborted due to empty content."); return }
commentTextView.resignFirstResponder()
commentTextView.text = ""
calculateTextViewSize()
ContentUploader.uploadComment(uid: user.uid, content: NativeContentBase(msg: commentContent, usersTagged: [], topicsTagged: [], mediaAssigned: []), referencedContent: tlContentItem.nativeContent.contentId) { (error, comment) in
if let err = error {
print("An error occured during the comment upload: ", err)
return
}
print("Successfully uploaded comment!")
self.commentItems.append(comment)
DispatchQueue.main.async {
print(self.commentItems.count)
self.timelineCollectionView.reloadData()
}
}
print("Comment scheduled for submission!")
}
I also made sure that I am in the main thread when calling the reloadData() functionality. However, the weird behavior persists.
In order to adjust the collectionView's contentInset and scrollIndicatorInsets to the display of the keyboard, I am using these two functions:
private func adjustScrollBehavior() {
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let bottomSafeAreaHeight = window?.safeAreaInsets.bottom ?? 0
// adjust the content Inset of the timelineCollectionView
let heightOfCommentSection: CGFloat = commentTextViewHeightAnchor.constant + abs(commentTextViewBottomAnchor.constant) + 8 // get the absolute value of the bottomAnchor's constant as it would be negative
timelineCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: heightOfCommentSection + bottomSafeAreaHeight, right: 0) // does not automatically take care of safe area insets
timelineCollectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: heightOfCommentSection, right: 0) // automatically takes care of safe area insets
}
func calculateTextViewSize() {
let minimumSize = commentTextView.layoutMargins.top + commentTextView.layoutMargins.bottom + commentTextView.font!.lineHeight
let fixedWidth = self.view.frame.width - 32
let maximumSize: CGFloat = 155
let newSize = commentTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
print(newSize)
if newSize.height > minimumSize && newSize.height <= maximumSize {
commentTextViewHeightAnchor.constant = newSize.height
} else {
commentTextViewHeightAnchor.constant = newSize.height > minimumSize ? maximumSize : minimumSize
}
adjustScrollBehavior()
}
Closing
I know this is a lot of code, but basically, that's everything I use. I have no idea why this happens, and I would be incredibly grateful for any help I could get.
Thanks a lot for your help :)
I am creating a chat feature in my app, but the cells are getting cut off. I've reviewed the Ray Wenderlich tutorial and the SO questions, but I think my problem is that I combine code with auto layout and I'm missing something (for instance, I don't put a label for the message text on the storyboard, but I do put the imageView). Here's what it looks like when I run it:
Here is the code for the tableView setup:
In viewDidLoad:
tableView.estimatedRowHeight = 600
tableView.rowHeight = UITableView.automaticDimension
Other code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a preconfigured messageCell
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Storyboard.messageCell) as! MessageCell
// Send the message to it for set up.
cell.setMessage(message: messages[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
And here is the setMessage function in the custom cell:
#IBOutlet var messageImage: UIImageView!
func setMessage(message: Message) {
if message.who == "me" {
// User wrote this message, show it on the right in blue as an outgoing message.
let text = message.txt
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = .white
label.text = text
let constrainRect = CGSize(width: 0.66 * self.frame.width,
height: .greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constrainRect, options: .usesLineFragmentOrigin, attributes: [.font: label.font!], context: nil)
label.frame.size = CGSize(width: ceil(boundingBox.width), height: ceil(boundingBox.height))
let bubbleImageSize = CGSize(width: label.frame.width + 28, height: label.frame.height + 20)
messageImage = UIImageView(frame: CGRect(
x: self.frame.width - bubbleImageSize.width - 20,
y: self.frame.height - (bubbleImageSize.height),
width: bubbleImageSize.width,
height: bubbleImageSize.height))
let bubbleImage = UIImage(named: "outgoing-message-bubble")?
.resizableImage(withCapInsets: UIEdgeInsets(top: 17, left: 21, bottom: 17, right: 21), resizingMode: .stretch)
.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
messageImage.image = bubbleImage
self.addSubview(messageImage)
label.center = messageImage.center
self.addSubview(label)
} else {
// Other person wrote this message. Show it on the left in gray.
}
}
There are no constraints on messageImage, but I have tried it with constraints of zero around it and the result is the same. I've run the code with breaks and confirmed the sizes are all accurate for the text and the bubble image. But the content view of the cell is cutting off the height to a standard cell size. I've worked on this for over a day. Any help would be appreciated.
Creating timetable for school, Implemented the design using UITableView
2 days and 6 periods, periods is dynamic it can show n numbers.
My code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dayArr.count-1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:PeriodCell = tableView.dequeueReusableCell(withIdentifier: "PeriodCell") as! PeriodCell
cell.selectionStyle = .none
tableView.separatorStyle = .none
var x: Int = 2
for i in 0 ..< periodArr.count
{
let btn: UIButton = UIButton(frame: CGRect(x: x, y: 0, width: 110, height: 45))
btn.backgroundColor = UIColor.clear
btn.setTitle("Select", for: .normal)
btn.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
btn.tag = indexPath.row
btn.layer.cornerRadius = 5
btn.layer.borderWidth = 1
btn.layer.borderColor = UIColor.lightGray.cgColor
btn.setTitleColor(UIColor.black, for: .normal)
cell.contentView.addSubview(btn)
x = x + 115
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
My button click action:
#objc func buttonAction(sender: UIButton!) {
let btnsendtag: UIButton = sender
print("btnsendtag",btnsendtag.tag)
sender.setTitle("Tamil",for: .normal)
for i in 0 ..< periodArr.count
{
print("btnsendtag",btnsendtag.tag)
if let buttonTitle = btnsendtag.title(for: .normal)
{
print("buttonTitle",buttonTitle)
}
}
}
My problem:
1. I want to get each button title. Monday and Tuesday separate as array separate and send to webservices [or] get the all button title and send it comma separate to webservices .[periods is dynamic it can show n numbers] want to get all title. Day only 6 days always shown.
How to identify which button is selected, which button value is adding and without selected button want to send empty title to webservies. How can identify.
How to write the code for button click action. Please help me, I am struggling on this from a week. Thanks in advance.
You can go for header item concept to archive this in collection view easily for the table view it's really hard to get this type of thing what you are asking for.
I had used NHRangeSlider for setting price range in my application placed in table view in which after setting the min and max ranges when i move to another view controller i was getting error and the last set values and normal values has been set in the slider how to clear this error ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderCell
tableView.isHidden = false
activityIndicator.stopAnimating()
activityIndicator.hidesWhenStopped = true
let sliderView = NHRangeSliderView(frame: CGRect(x: 16, y: 28, width: self.view.bounds.width - 32, height: 80))
sliderView.sizeToFit()
cell.contentView.addSubview(sliderView)
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "brandCell", for: indexPath) as! BrandCell
if selected == true{
let value = values.count
if (value == 1) {
cell.brandsLabel.text = values[indexPath.row]
}
else if (value == 0) {
let string = "no brand selected"
cell.brandsLabel.text = string
}
else {
let total = " &"+" \(value-1) more"
print(total)
cell.brandsLabel.text = "\(values[0])" + total
}
}
return cell
}
}
image is as shown here
It looks like you are always adding a new instance of NHRangeSliderView to your cell:
let cell = tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderCell
tableView.isHidden = false
activityIndicator.stopAnimating()
activityIndicator.hidesWhenStopped = true
let sliderView = NHRangeSliderView(frame: CGRect(x: 16, y: 28, width: self.view.bounds.width - 32, height: 80))
sliderView.sizeToFit()
cell.contentView.addSubview(sliderView)
return cell
Assuming that you are reloading the cell/tableview somewhere else you will find that this piece of code is always adding a brand new slider view to the content view of your cell, since it is reusing the cell that already has an instance of the NHRangeSliderView in it.
In this situation I would recommend creating a custom table view cell that already has the slider view attached this way you will have a slightly simpler cellForRow(atIndexPath:) method, but also you won't need to worry about continually adding the view.