Swift UITableView calls didSelectRowAt only if I press a few seconds - ios

I have a UITableView with customs UITableViewCell, but I'm having a strange issue, if I want the didSelectRowAt func to be called I need to press the cell (and stay pressed) for 2 seconds.
I tried with the didHighlightRowAt func, that takes less time (1 second) but I still need to press and wait for a second, if I do not, nothing happens.
my cellForRowAt func is the following one (I don't know if that can be related ?) :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = StationSearchCellView(style: .default, reuseIdentifier: "cell", station: self.tableSearchesData[indexPath.section])
cell.selectionStyle = .none
return cell
}
My custom cell is :
class StationSearchCellView : UITableViewCell {
var stationName : String? init (
style : UITableViewCellStyle,
reuseIdentifier : String?,
station : String
)
{
super.init (style: style, reuseIdentifier: reuseIdentifier)
self.stationName = station let frameHeight = FDUtils.shared.heightRelativeToLayout (heightInPixels: 60)
let iconSize = FDUtils.shared.widthRelativeToLayout (widthInPixels: 16)
let marginWidth = FDUtils.shared.widthRelativeToLayout (widthInPixels: 20)
// separator
let separator = UIView (
frame : CGRect (x: 0, y: 0-0.5, width: self.frame.width, height: 1)
)
separator.backgroundColor = FDColors.gray707070.withAlphaComponent (0.50)
// Label Station
let labelStation = UILabel (
frame : CGRect (
x : 0,
y : 0,
width : FDUtils.shared.elementsWidth - iconSize - marginWidth,
height : frameHeight
)
)
labelStation.text = station labelStation.setBlack_Left_Regular16 ()
// Image fleche
let searchIcon = UIImageView (
frame : CGRect (
x : labelStation.frame.width,
y : labelStation.frame.height / 2 - iconSize / 2,
width : iconSize,
height : iconSize
)
)
searchIcon.image = UIImage (named: "blue_select_icon")
self.addSubview (separator)
self.addSubview (labelStation)
self.addSubview (searchIcon)
}
required init? (coder aDecoder: NSCoder)
{
fatalError ("init(coder:) has not been implemented")
}
}
Any idea?
Thanks

Your problem will be conflicting gesture recogizers.
See this Apple article for some more details.
The easiest method to solve your issue would be to let the touch events passthrough to those below, you can do this by setting cancels touches in view to false on your gesture recogniser.
tapGesture.cancelsTouchesInView = false

Related

UILabel not breaking lines in UITableViewCell

I'm building an app with a messenger like interface. I use a tableView to accomplish this. Each cell contains a UIView - the message bubble and a UILabel - the message that is nested in the UIView.
It works great on texts of small sizes but for some reason when the UILabel is supposed to break lines it doesn't and it all is in one line. The amount of lines is set to zero.
This is my message handling class:
func commonInit() {
print(MessageView.frame.height)
MessageView.clipsToBounds = true
MessageView.layer.cornerRadius = 15
myCellLabel.numberOfLines = 0
let bubbleSize = CGSize(width: self.myCellLabel.frame.width + 28, height: self.myCellLabel.frame.height + 20)
print(bubbleSize.height)
MessageView.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: bubbleSize.width, height: bubbleSize.height)
if reuseIdentifier! == "Request" {
MessageView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMinXMaxYCorner]
MessageView.backgroundColor = UIColor(red: 0, green: 122/255, blue: 1.0, alpha: 1.0)
} else {
MessageView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner]
MessageView.backgroundColor = UIColor.lightGray
}
}
Cell calling function:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if queryCounter % 2 == 0 && indexPath.row % 2 == 0{
cellReuseIdentifier = "Answer"
} else {
cellReuseIdentifier = "Request"
}
let cell:MessageCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MessageCell
cell.myCellLabel.textColor = UIColor.white
cell.myCellLabel.text = self.messages[indexPath.row]
let height = cell.myCellLabel.text!.height(withConstrainedWidth: cell.myCellLabel.frame.width, font: cell.myCellLabel.font)
print(height)
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
return cell
}
The height variable is calculated based on text size. It shows that the text size is calculated normally - accounting for line break.
I was unable to modify the cell height based on this calculation - nothing I tried works.
I think it might be a constraints issue.
My Constraints:
How do I make the lines break? Please help.
EDIT: I just notice that the MessageView.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: bubbleSize.width, height: bubbleSize.height) has no affect what so ever on the message bubbles.
Setting the frame while using autolayout won't work.
I can't say what exactly happens here without the entire context, but some common pitfalls when reusing cells and autolayout are:
Forgetting to set automatic height for your cells (expanding cell in a storyboard manually will override this setting)
Tableview also needs estimatedHeight sometimes to work properly
Sometimes you need to call setNeedsLayout after you add content to
the cell
Check the console and if there are some warnings about breaking constraints, you can easily find issues there.
Try to find the label height based on label width and text font and then set your label height constraint to that.
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
return ceil(boundingBox.height)
}
}
something like this:
let textHeight = yourtext.height(withConstrainedWidth: yourlabel.frame.width, font: font)
yourLabel.heightAnchor.constraint(equalToConstant: textHeight).isActive = true
After multiple hours of trying everything I managed to fix it. The problem was with the constraints.
1. As you can see the in this old layout. The UIView was constrained everywhere except the left -> that's where the text goes.
The commonInit() method of the UITableViewCell was called before any text was initialized. That's not good because all of the cell resizing is based on text which was not yet passed to the cell -> Move the method after cell initialization.
let cell:MessageCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MessageCell
cell.myCellLabel.text = self.messages[indexPath.row]
//Before calling commonInit() we need to adjust the cell height.
let height = cell.myCellLabel.text!.heightForView(text: cell.myCellLabel.text!, font: cell.myCellLabel.font, width: self.view.frame.width / 2)
// Then we set the width of the UILabel for it to break lines at 26 characters
if cell.myCellLabel.text!.count > 25 {
tableView.rowHeight = height + 20
cell.myCellLabel.widthAnchor.constraint(equalToConstant: cell.frame.width / 2).isActive = true
cell.updateConstraints()
}
// Calling commonInit() after adjustments
cell.commonInit()
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
return cell
Then we need to update the constraints so that the UIView and UILabel resize with the cell height.
Done. Now it works as needed. Thank you for all of the suggestions!

ios swift 3 delete contents of each cell when reloading tableView

I am creating a quizApp using DLRadioButton with a label in each cell of tableView, the label is set from the storyBoard(with a tag) but I have set radioButtons programmatically for better design when I click next button to pass to next question new Radiobutton overlaps with the old Radiobutton
here is the first question
here is the second image
Below is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")!
var reponse = rep[(indexPath as NSIndexPath).row]
let content:UILabel=cell.viewWithTag(111) as! UILabel
//var ra:UIButton=cell.viewWithTag(112) as! UIButton
content.text=reponse["rep"] as? String
content.sizeToFit()
let repiii = reponse["rep"] as? String
// var radio = DLRadioButton()
if (self.type=="case à cocher"){
// cell.contentView.delete(radioButtons)
let frame = CGRect(x: 50, y: 20, width: 200, height: 40)
let squareButton = self.createRadioButton(frame: frame, title:repiii!, color: UIColor.red,compt: squareButtons.count,sqaure:1);
squareButtons.append(squareButton)
cell.contentView.addSubview(squareButton)
}
else{
let frame = CGRect(x: 50, y: 20, width: 200, height: 40)
let radioButton = self.createRadioButton(frame: frame, title:repiii!, color: UIColor.blue,compt: radioButtons.count,sqaure:0);
//radioButton.isMultipleSelectionEnabled = true
print(radioButtons.count)
radioButtons.append(radioButton)
cell.contentView.addSubview(radioButton)
}
return cell
}
any suggestion is appreciated
in your code, just after the line
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")!
add this:
for subview in cell.contentView.subviews {
subview.removeFromSuperView()
}

Some tableview cell content disappears after scroll (swift)

Below is my custom cell class:
class AthleteTableViewCell: UITableViewCell {
var myLabel1: UILabel!
var myLabel2: UILabel!
var profile: UIImageView!
var star = StarButton()
var touched = false
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let gap : CGFloat = 10
let labelHeight: CGFloat = 30
let labelWidth: CGFloat = 150
let lineGap : CGFloat = 5
let label2Y : CGFloat = gap + labelHeight + lineGap
myLabel1 = UILabel()
myLabel1.frame = CGRect(x: gap, y: gap, width: labelWidth, height: labelHeight)
myLabel1.textColor = UIColor.black
contentView.addSubview(myLabel1)
myLabel2 = UILabel()
myLabel2.frame = CGRect(x: gap * 5, y: label2Y, width: labelHeight, height: labelHeight)
myLabel2.textColor = UIColor.white
myLabel2.textAlignment = .center
myLabel2.backgroundColor = UIColor.flatMint()
myLabel2.layer.cornerRadius = 15.0
myLabel2.clipsToBounds = true
contentView.addSubview(myLabel2)
profile = UIImageView()
profile.image = UIImage()
profile.frame = CGRect(x: bounds.width - gap, y: bounds.height / 2, width: bounds.height * 1.25, height: bounds.height * 1.25)
profile.layer.cornerRadius = (bounds.height * 1.25) / 2
profile.layer.masksToBounds = true
contentView.addSubview(profile)
if (touched != false) {
star.isSelected = true
star.frame = CGRect(x: gap, y: label2Y, width: labelHeight, height: labelHeight)
contentView.addSubview(star)
} else {
star.frame = CGRect(x: gap, y: label2Y, width: labelHeight, height: labelHeight)
contentView.addSubview(star)
star.isEnabled = true
}
}
}
and below is the method for creating my cells:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = AthleteTableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "myCell")
cell.myLabel1.text = "\(myArray[indexPath.row])"
cell.myLabel1.textColor = UIColor.white
cell.myLabel2.isHidden = true
cell.profile.image = UIImage(named: cell.myLabel1.text!)
cellArray.append(cell)
if (cell.touched) {
cell.star.isSelected = true
} else {
cell.star.isOpaque = true
}
cell.backgroundColor = UIColor(white: 1, alpha: 0.2)
return cell
}
and below is the method for selecting a cell which trips an animation that I would like the final state of to persist on the tableview cell.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
makeSelection(tableView, didSelectRowAt: indexPath)
}
func makeSelection(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! AthleteTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.none
if(selectionArray.contains(myArray.object(at: indexPath.row))) {
//Popping selection off of the stack and updating all labels
updateLabels(tableView: tableView)
selectionArray.remove(myArray[indexPath.row])
cell.star.isFavorite = false
cell.myLabel2?.isHidden = true
//need to somehow implement all of the rank labels decreasing by one if I deselect someone
cell.touched = false
//updateLabels(tableView: tableView)
} else {
selectionArray.add(myArray[indexPath.row])
cell.touched = true
let rank = selectionArray.count
cell.myLabel2.isHidden = false
cell.myLabel2?.text = "\(rank)"
cell.star.isFavorite = true
}
}
This is a photo of what a cell looks like when you select it:
and this is a photo of that same cell after scrolling down so that is out of view and then scrolling back:
Something clearly goes very wrong here - I added that "touched" boolean to the custom table view cell class under the assumption that maybe the cells were being redrawn and there was no way for it to know whether it should have that star animated or not so it was considered nil but that fix doesn't seem to work (maybe I am onto something but implemented it incorrectly?)
Let me know if you have any clue what is going on here! thanks so much!!!
Your code has a variety of problems. In your tableView(_:cellForRowAt:) method, you should be calling dequeueReusableCell(withIdentifier:for:).
You should not be trying to read data from the cells in order to load content. You should save state data to a data model of some sort. An array works well for a table view with a single section, and an array of arrays works well for a sectioned table view.
There are countless tutorials on how to use table views. You need to go do some reading.
Mentioning it as an answer here as a step by step process
1) Create Struct for your data
struct Profile {
var personName: String?
var photoURL: String?
var rank: String?
var isTouched: Bool?
// add init method and stuff
}
2) Populate your array using these Profile elements. So that in the end you have an array of profiles.
3) Use this array to populate your tableview with each cell element getting data from the Profile object.
4) in your cellForRowAt method use dequeueReusableCell(withIdentifier:for:) to initialize your cell and assign values
5) if for that particular profile, isTouched is true then show
cell.star.isFavorite = true
else set it to false
6) in your didSelectRow, get indexpath.row and then in your array of profiles set Profile.isTouched=true/false based on your needs for that particular row and toggle accordingly. Update your profile array with new profile object.
7) refresh tableview.

Set width and height of UITextView based on the dynamic text

I'm building a chat application where each message are inserted into a row inside a table. Each row contains an avatar and a message. I want to set the width and height of the UITextArea as per the length of the text to put inside.
Below is the code I've used. But here, both height and width are constant (200x50)
PS: I'm a newbie to Swift and ios and I'm using Swift 3. Every code I got after searching is in objective-c or swift 2
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("ChatBox1", owner: self, options: nil)?.first as! ChatBox1
let myTextField: UITextView = UITextView(frame: CGRect(x: 30, y: 20, width: 200.00, height: 50.00));
cell.addSubview(myTextField)
myTextField.isScrollEnabled = false
myTextField.isUserInteractionEnabled = false
myTextField.backgroundColor = UIColor.lightGray
myTextField.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
myTextField.layer.cornerRadius = 5
print(myTextField.intrinsicContentSize)
let image = UIImage(named: "agent")
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
cell.addSubview(imageView)
return cell
}
This two methods are done to set Height and width of textview according to the text on chat.
You can use these. (swift 2.3)
var screenrect = UIScreen.mainScreen().bounds
func GET_WIDTH(textView : UITextView , text : String)-> CGFloat
{
textView.text = text;
let size = textView.bounds.size
let newSizeWidth = textView.sizeThatFits(CGSize(width: CGFloat.max, height: size.height))
// Resize the cell only when cell's size is changed
if size.width != newSizeWidth.width
{
if( newSizeWidth.width > screenrect.width-120 )
{
textView.layer.frame.size.width = screenrect.width-120;
}
else if ( newSizeWidth.width < 40 )
{
textView.layer.frame.size.width = 40
}
else
{
textView.layer.frame.size.width = newSizeWidth.width;
}
}
return textView.layer.frame.size.width + 40;
}
func GET_HEIGHT(textView : UITextView , text : String)-> CGFloat
{
textView.text = text;
let size2 = textView.bounds.size
let newSize = textView.sizeThatFits(CGSize(width: size2.width, height: CGFloat.max))
if size2.height != newSize.height
{
if( newSize.height < 40 )
{
textView.layer.frame.size.height = 40
}
else
{
textView.layer.frame.size.height = newSize.height
}
}
return textView.layer.frame.size.height + 15;
}
You have to call the functions of the textview like this to set height and width.
let textViewRight=UITextView(frame: CGRectMake(0, 4, 40, 40));
textViewRight.layer.frame.size.width = GET_WIDTH(textViewRight, text: currentObject.chat_userMessage)
textViewRight.layer.frame.size.height = GET_HEIGHT(textViewRight, text: currentObject.chat_userMessage)
Try this code:
Answer 1:
func tableView(tableView:UITableView!, numberOfRowsInSection section: Int) -> Int {
yourTableViewName.estimatedRowHeight = 44.0 // Standard tableViewCell size
yourTableViewName.rowHeight = UITableViewAutomaticDimension
return yourArrayName.count }
And also put this code inside your Cell for incase...
yourCell.sizeToFit()
Answer 2:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
in viewDidLoad Add this
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
Just add these two lines and your problem will b solved
Take textView height constraint and set it equal to the content size of the textView.
Alternatively, if you use autolayout you can set the content hugging and compression property to 1000. It should expand your cell according to the content.

Oblique table view cells in iOS

How can I implement this? A table view with oblique cells. I was thinking first to make the cells overlap and cut out a piece, but I can't make it work.
Now the single solution I can think of is to download the second image in first cell and cut out the top triangle and add to the first cell. But that wouldn't be too optimal memory wise and processing wise if the user is scrolling through the list.
I appreciate any advice, thank you!
I've done something similar like this but I didn't use UITableView or UICollectionView.
What I use is having one UIImageView(AView) on top of the other UIImageView(BView) in view hieararchy. So I add UISwipeGestureRecognizer to BView .And at the top of BView I've added a seperator view(when VC load user could not see it). And I basically animating BView however I want.
But since they are not cells in UITableView you can't give "pulling" behavior to user which is a UX disadvantage for your app.
I solved it the following way. I used UICollectionView in the end because I couldn't find a way to overlap nicely the cells from UITableView. So first I made a custom layout which looks like this:
CustomCollectionViewFlowLayout.swift
import UIKit
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func collectionViewContentSize() -> CGSize {
let xSize = self.itemSize.width
let ySize = CGFloat(self.collectionView!.numberOfItemsInSection(0)) * self.itemSize.height
var size = CGSizeMake(xSize, ySize)
if self.collectionView!.bounds.size.width > size.width {
size.width = self.collectionView!.bounds.size.width
}
if self.collectionView!.bounds.size.height > size.height {
size.height = self.collectionView!.bounds.size.height
}
return size
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
let attributesArray = super.layoutAttributesForElementsInRect(rect) as! [UICollectionViewLayoutAttributes]
let numberOfItems = self.collectionView?.numberOfItemsInSection(0)
for attribute in attributesArray {
let xPosition = attribute.center.x
var yPosition = attribute.center.y
if attribute.indexPath.row == 0 {
attribute.zIndex = Int(INT_MAX)
} else {
yPosition -= CGFloat(60 * attribute.indexPath.row)
attribute.zIndex = numberOfItems! - attribute.indexPath.row
}
attribute.center = CGPointMake(xPosition, yPosition)
}
return attributesArray
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
return UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
}
}
Then I used paths and shapes to cut out that part and also draw a white rectangle. So it looks like this:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ChallengeCollectionViewCell", forIndexPath: indexPath) as! ChallengeCollectionViewCell
cell.challengeImage.sd_setImageWithURL(NSURL(string: STATIC_IMAGE_URL), completed: nil)
let trianglePath = CGPathCreateMutable()
CGPathMoveToPoint(trianglePath, nil, 0, 0)
CGPathAddLineToPoint(trianglePath, nil, 0, cell.challengeImage!.frame.size.height)
CGPathAddLineToPoint(trianglePath, nil, cell.challengeImage!.frame.size.width, cell.challengeImage!.frame.size.height - 50)
CGPathAddLineToPoint(trianglePath, nil, cell.challengeImage!.frame.width, 0)
CGPathAddLineToPoint(trianglePath, nil, 0, 0)
CGPathCloseSubpath(trianglePath)
let shapeLayer = CAShapeLayer()
shapeLayer.frame = cell.challengeImage!.frame
shapeLayer.path = trianglePath
cell.challengeImage.layer.mask = shapeLayer
cell.challengeImage.layer.masksToBounds = true
let linePath = CGPathCreateMutable()
CGPathMoveToPoint(linePath, nil, 0, cell.challengeImage!.frame.size.height)
CGPathAddLineToPoint(linePath, nil, cell.challengeImage!.frame.size.width, cell.challengeImage!.frame.size.height - 50)
CGPathCloseSubpath(linePath)
let lineShape = CAShapeLayer()
lineShape.path = linePath
lineShape.lineWidth = 10
lineShape.strokeColor = UIColor.whiteColor().CGColor
cell.challengeImage.layer.addSublayer(lineShape)
return cell
}
I hope it will be useful for someone who runs into the same problem. Thanks and best wishes!

Resources