TableView Cells / Visual Format Language / Keeps adding same constraints? - swift

Creating UITableViewCells including elements programmatically seems to be working except for the constraints. Or maybe it is. Everytime the cell reappears the same constraints will be added to the element. I'll have 20 of the same constraints added to a uiLabel after scrolling tableView. Here is my sublcass (condensed):
class TimeSlotCell: UITableViewCell {
let lblTime = UILabel()
var viewCons = [String:AnyObject]()
var previousCageView:UIView!
var myConstraints = [NSLayoutConstraint]()
func configureCell(row:Int,cageCount:Int) {
lblTime.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(lblTime)
placeConstraint("H:|-5-[time\(row)]", view:"time\(row)")
placeConstraint("V:|-17-[time\(row)]", view: "time\(row)
}
func placeConstraint(format:String, view:String) {
viewCons[view] = lblTime
let navViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: viewCons)
myConstraints += navViewConstraint
NSLayoutConstraint.activateConstraints(myConstraints)
}
And in my VC i'm calling the configureCell func within my cellForRowAtIndexPath.
Is there a way to check if an existing constraint exists? Is there a way to make sure only one of my constraints can be added? Or is there a person out there with a much better solution? Thanks in advance.

There is a dataSource for your tableView, when you configure your cell, you send the cell model to your cell. Whenever the dataSource changes, the cell model sent to the cell will change as well. So what you need to do is move most of 'addSubview' to the cell`s initialization. If you have to do cell.contentView.addSubview with the cell model. You have to judge if the cell model you send to the cell is the same as the former one. If it is equal, do nothing, else reset the view config.

Related

How Do I Add a Parallax Effect To A UITableView Cell

Hello I am creating a sort of social media app and I am using table view to display all the information, I have made a parallax effect and at first it works nicely and smoothly, but after couple of times of moving my device it becomes supper jittery and way to quick
Here is the extension that I have created that I am adding to my TableViewCellView: (I call it like this: myCell.addParallax(magnitude: 10) )
extension UIView {
func addParallax(magnitude: Float) {
let xMotion = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
xMotion.maximumRelativeValue = magnitude
xMotion.minimumRelativeValue = -magnitude
let yMotion = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
yMotion.maximumRelativeValue = magnitude
yMotion.minimumRelativeValue = -magnitude
let motionEffect = UIMotionEffectGroup()
motionEffect.motionEffects = [xMotion, yMotion]
self.addMotionEffect(motionEffect)
}
}
Your UITableViewCells are being reused while the table is scrolled by the user. In the cellForRow atIndexPath method you're most likely using tableView.dequeueReusableCell method. This method doesn't create new cells all the time, it reuses the ones that are not visible anymore.
The UIMotionEffectGroup you're creating is being added to the UITableViewCell each time the myCell.addParallax(magnitude: 10) is called. In the end, you're having many UIMotionEffectGroup which is the source of your problem. Make sure that this method is called only once per cell, you can use awakeFromNib method in the cell itself to achieve this.

Add custom recognizer delay

I've disabled delaysContentTouches in my tableview subclass using:
delaysContentTouches = false
subviews.forEach { ($0 as? UIScrollView)?.delaysContentTouches = false }
But in one of my sections, I still want to keep the delay. Is there a way to cancel the delay for certain sections or perhaps I can add a custom recognizer delay to a section?
Sections are not actual objects within a tableView, so my answer to your first question is no. The .delaysContentTouches applies to the entire tableView.
For your second inquiry, I believe that one way it could be possible is through setting a delay for desired cells' scrollView subview. In your tableView(cellForRowAt: indexPath) func, you could have something like this:
if indexPath.section == 3 { //or whatever your desired section is
for view in cell.subviews {
if view is UIScrollView {
let currentView = view as! UIScrollView
currentView.delaysContentTouches = true
}
}
}
This will find the UIScrollView in your cell's subviews in your desired section. It will then set the .delaysContentTouches property accordingly.
I have not personally executed this code, just researched it, so let me know if it works.
Edit
Apparently the UIScrollView in UITableViewCell has been deprecated, so the above method will not work anymore.
My next best suggestion to you is to use a UILongPressGuestureRecognizer. This will not be quite the same thing as delaying the touch, but could have a similar effect in real execution.
You could use it in the same tableView(cellForRowAt: indexPath) func as so:
let press = UILongPressGestureRecognizer(target: self, action: #selector(handlePress))
press.minimumPressDuration = 2.0 //however long you want
cell.addGestureRecognizer(press)
Whatever you are trying to achieve by selecting certain rows of your tableView could be placed in the handlePress func above which would be trigged upon the long press.

How can I keep the first row of a UITableView focused during a data update?

I have a UITableView that presents a timeline of data on tvOS. It is dynamically updated via an NSFetchedResultsController.
When the table is updated, new cells are added at the top. However: the previously selected cell remains focused, but the behaviour I need is for the focus to shift to the 'newest' (i.e. topmost) cell after the data update.
How can I achieve this?
Not sure what your code looks like, but you can create a property to keep track of whether the table is actively updating.
var tableViewIsUpdating = false
You can use this to determine if you want the normal focus before or to return the first row of your table view:
override var preferredFocusedView: UIView?{
get {
if updating {
return self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0))
}
return super.preferredFocusedView
}
}
Then in your NSFetchedResultsControllerDelegate set if it's updating or not:
func controllerWillChangeContent(controller: NSFetchedResultsController){
self.tableViewIsUpdating = true
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.setNeedsFocusUpdate()
self.updateFocusIfNeeded()
self.updating = false
}
You can find more information here on how to update focus programmatically.

Table with static cells won't scroll when I set delaysContentTouches = false

The problem: HIGHLIGHT vs SCROLLING
My buttons inside the cell where not getting highlighted when I lightly tap on them. I had to tap hard and for a long time to be able to see the tap state of the button.
So I did this in order to set the delaysContentTouches to false (I didn't manage other way to do it) inside viewDidLoad():
for index in tableView.subviews {
if (index.isKindOfClass(UIScrollView)) {
let scrollViewFound = index as! UIScrollView
scrollViewFound.delegate = self
scrollViewFound.delaysContentTouches = false
scrollViewFound.canCancelContentTouches = true
scrollViewFound.scrollEnabled = true
}
}
This way the buttons highlight correctly but then I cannot scroll the table up or down, unless I start dragging from one of the empty cells --> userInteractionEnable = false in the empty cells
What I need:
To be able to highlight the buttons but also to scroll the table.
Is it even possible to have both, scrollable view and highlighted buttons?
What I have tried
I tried calling this function:
func touchesShouldCancelInContentView(view: UIView) -> Bool {
print("touchesShouldCancelInContentView happening---------")
return true
}
Which never gets called. I tried overriding But it gives an error:
Method does not override any method from its superclass
Which is weird, because UITableViewController inherits from UIScrollView. I also tried adding UIScrollViewDelegate to the class definition, but of course it gives another error that this is redundant.
Extra Information
The class is declared like this:
class Settings: UITableViewController, UITextFieldDelegate { ...
The tableView is made of Static Cells
The cells:
Some are empty: where UserInteractionEnable = false
Some have buttons with text field: I want these buttons to get highlighted. UserInteractionEnable = true. The button action is called by .TouchUpInside
Some have labels and a check image: Their action gets called in didSelectRowAtIndexPath which will change the labels colour and check images
Maybe it is relevant to say that when user clicks on any cell didSelectRowAtIndexPath it will
call a function to dismiss the keyboard.
You tried to subclass the wrong class, that's why it doesn't work. You have to subclass the UITableView class itself, and not the UITableViewController.
Can you try the following ?
- First
Subclass the TableView class in order to override the touchesShouldCancelInContentView function.
class UIDraggableTableView: UITableView {
override func touchesShouldCancelInContentView(view: UIView) -> Bool {
if (view.isKindOfClass(UIButton)) {
return true
}
return super.touchesShouldCancelInContentView(view)
}
}
- Second
In your TableViewController class, when viewDidLoad() is called, append the following right after super.viewDidLoad():
self.tableView = DraggableTableView()
This should solve your issue.
Part of this answer was taken from this StackOverflow post.

UITableView - How to keep table rows fixed as user scrolls

I'd like to be able to fix the position of certain rows in a UITableView as the user scrolls.
Specifically, I have a table whereby certain rows are "headers" for the rows that follow, and I'd like the header to stay at the top of the screen as the user scrolls up. It would then move out of the way when the user scrolls far enough that the next header row would take its place.
A similar example would be the Any.DO app. The "Today", "Tommorrow" and "Later" table rows are always visible on the screen.
Does anyone have any suggestions about how this could be implemented?
I'm currently thinking of follow the TableDidScroll delegate and positioning my own cell in the appropriate place in front of the table view. The problem is that at other times I'd really like these cells to be real table cells so that they can be, for example, reordered by the user.
Thanks,
Tim
I've been playing about with this and I've come up with a simple solution.
First, we add a single UITableViewCell property to the controller. This should be initialize such that looks exactly like the row cells that we'll use to create the false section headers.
Next, we intercept scrolling of the table view
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// Add some logic here to determine the section header. For example, use
// indexPathsForVisibleRows to get the visible index paths, from which you
// should be able to get the table view row that corresponds to the current
// section header. How this works will be implementation dependent.
//
// If the current section header has changed since the pervious scroll request
// (because a new one should now be at the top of the screen) then you should
// update the contents.
IndexPath *indexPathOfCurrentHeaderCell = ... // Depends on implementation
UITableViewCell *headerCell = [self.tableView cellForRowAtIndexPath:indexPathOfCurrentHeaderCell];
// If it exists then it's on screen. Hide our false header
if (headerCell)
self.cellHeader.hidden = true;
// If it doesn't exist (not on screen) or if it's partially scrolled off the top,
// position our false header at the top of the screen
if (!headerCell || headerCell.frame.origin.y < self.tableView.contentOffset.y )
{
self.cellHeader.hidden = NO;
self.cellHeader.frame = CGRectMake(0, self.tableView.contentOffset.y, self.cellHeader.frame.size.width, self.cellHeader.frame.size.height);
}
// Make sure it's on top of all other cells
[self.tableView bringSubviewToFront:self.cellHeader];
}
Finally, we need to intercept actions on that cell and do the right thing...
That's the default behavior for section headers in plain UITableView instances.
If you want to create a custom header, implement the tableView:viewForHeaderInSection: method in your table view delegate and return the view for your header.
Although you will have to manage sections and rows instead of just rows.
Swift 5 solution
var header: UIView?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(indexPath: indexPath) as UITableViewCell
header = cell.contentView
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let headerCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0))
guard headerCell == nil || (headerCell!.frame.origin.y < self.tableView.contentOffset.y + headerCell!.frame.height/2) else {
header?.isHidden = true
return
}
guard let hdr = header else { return }
hdr.isHidden = false
hdr.frame = CGRect(x: 0, y: tableView.contentOffset.y, width: hdr.frame.size.width, height: hdr.frame.size.height)
if !tableView.subviews.contains(hdr) {
tableView.addSubview(hdr)
}
tableView.bringSubviewToFront(hdr)
}