UITableViewCell overlapping issue - ios

Hello
Now I've been trying to display html links in a UITableView.
I've been trying to do this via adding instances of UITableViewCell to the tableview's subview.
func updateViewController(){
for name : String in currentDirectoryNames {
var pusher = UITableViewCell();
pusher.textLabel?.text = name;
listView.addSubview(pusher);
}
}
Sadly the result ends up with the text overlapped in one row :(
It looks like this...
Any ideas?

That's not how UITableView works, at all. I highly suggest you read Apple's documentation on TableView Programming. At the very least, you'll need to set your view controller as the dataSource for the table view and implement the -tableView:numberOfRowsInSection: and -tableView:cellForRowAtIndexPath: methods.

The func you're calling is adding all of your subviews with origin.y equal to 0 (which makes them look like they're all on top of each other). You may have to do something like set pusher.frame.origin.y to dynamically calculate based on which cell is being added.

Related

Should I use dynamic stackview inside UITableViewCell or use UICollectionView?

Hello. I am building flights booking app, which has complex UITableViewCell. Basically, it is simple card with shadow, that has bunch of stackviews. First stackview, you see it on image is for labels. It is horizontal and dynamic. The next stackview shows flights. It has complex custom view, but for the sake of simplicity, it is shown with green border. It is also dynamic, so I need separate stackview for it. The next stackview is for airline companies that can handle this booking. I call them as operators. It is also dynamic, so I build yet another stackview for them. And of all these stack views are inside some core stackview. You can ask, why I created separate stack views instead of one? Because, labels above can be hidden. And also spacing in all stackviews are different.
It is really complex design. I followed above approach and build UITableViewCell. But performance is really bad. The reason is simple: I do too many stuff in cellForRowAt. The configure method of UITableViewCell is called everytime when the cell is dequeued. It means I should clean my stackview every time and after only that, append my views. I think it is really affects performance. I don't tell about other if/else statements inside cell. The first question is how can I increase scrolling performance of UITableViewCell in this case?
Some developers reckons that UITableView should be killed. UICollectionView rules the world. OK, but can I use UICollectionView with this design? Yes, of course, but above card would be one UICollectionViewCell and I simply don't avoid problem. The another solution is to build separate UICollectionViewCell for label (see on image), flight and operator. This would definitely increase performance. But, how can I make all of them live inside card?
P.S. What is inside my cellForRowAt method? There is only one configure method and assigning values to closure. But configure method is pretty complex. It gets some protocol which has bunch of computed properties. I pass implementation of that protocol to configure method. Protocol is like this:
protocol Booking {
var flights: [Flight] { get }
var operators: [Operator] { get }
var labels: [Label] { get }
var isExpanded: Bool { get set }
}
Implementation of this protocol is also complex. There are bunch of map functions and if/else statements. Some string manipulations. So, does that cause a problem? How can I solve it? By avoiding properties to be computed and just pass properties(flights, operators) to the implementation?
As I said in my comment, without seeing complete detail, it's tough to help. And, it's a pretty broad question to begin with.
However, this may give you some assistance...
Consider two cell classes. In each, the "basic" elements are added when the cell is created -- these elements will exists regardless of actually cell data:
your "main" stack view
your "labels" stack view
your "flights" stack view
your "operators" stack view
To simplify things, let's just think about the "operators" stack view, and we'll say each "row" is a single label.
What you may be doing now when you set the data in the cell is something like this...
In the cell's init func:
// create your main and 3 sub-stackViews
Then, when you set the data from cellForRowAt:
// remove all labels from operator stack
operatorStack.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
// add new label for each operator
thisBooking.operators.forEach { op in
let v = UILabel()
v.font = .systemFont(ofSize: 15)
v.text = op.name
operatorStack.addArrangedSubview(v)
}
So, each time you dequeue a cell in cellForRowAt and set its data, you are removing all of the "operator" views from the stack view, and then re-creating and re-adding them.
Instead, if you know it will have a maximum of, let's say 10, "operator" subviews, you can add them when the cell is created and then show/hide as needed.
In the cell's init func:
// create your main and 3 sub-stackViews
// add 10 labels to operator stack
// when cell is created
for _ in 1...10 {
let v = UILabel()
v.font = .systemFont(ofSize: 15)
operatorStack.addArrangedSubview(v)
}
Then, when you set the data from cellForRowAt:
// set all labels in operator stack to hidden
operatorStack.arrangedSubviews.forEach {
$0.isHidden = true
}
// fill and unhide labels as needed
for (op, v) in zip(thisBooking.operators, operatorStack.arrangedSubviews) {
guard let label = v as? UILabel else { fatalError("Setup was wrong!") }
label.text = op.name
label.isHidden = false
}
That way, we only create and add "operator views" once - when the cell is created. When it is dequeued / reused, we're simply hiding the unused views.
Again, since you say you have a "really complex design", there is a lot more to consider... and as I mentioned you may need to rethink your whole approach.
However, the basic idea is to only create and add subviews once, then show/hide them as needed when the cell is reused.

Collapsible input form Swift 3

I am really getting disappointed that's why I ask this question, I found tutorials online for Collapsible TableView but they are with populating the cells with an array of Strings or something similar.
I want to make an Accordion like this with Swift 3,
for some days already I tried a lot of things with UITableViewController because apparently that's the only thing you can make collapsible if you want different cells.
Anyway I started to do it but as I asked my question here I cannot show different UIs in each Cell of each Section.
I am sure there's a way to do it, anyone has any suggestions how to make this work?
I thought maybe there's another way to do it (e.g. with ScrollView or something)
Here is how you could implement it with UIStackView:
Add a vertical UIStackView via storyboard
Add a UIButton as the first subview within the UIStackView
Add some UILabels as the second, third... subview within the UIStackView
Add an IBAction for the UIButton (the first subview)
Implement the IBAction like this:
#IBAction func sectionHeaderTapped(sender: UIButton) {
guard let stackView = sender.superview as? UIStackView else { return }
guard stackView.arrangedSubviews.count > 1 else { return }
let sectionIsCollapsed = stackView.arrangedSubviews[1].hidden
UIView.animateWithDuration(0.25) {
for i in 1..<stackView.arrangedSubviews.count {
stackView.arrangedSubviews[i].hidden = !sectionIsCollapsed
}
}
}
Create multiple UIStackViews like this (you can always use the same IBAction for the UIButton) and embed all of them in a parent (vertical) UIStackView to create a view like in your screenshot.
Feel free to ask if anything is unclear.
Result:

Simultaneously change display parameters on all table view cells

I am trying to implement a table view design where a user can click a button outside of a table view cell and the display mode of all the buttons should change. However this is not the 'selected' mode for a given cell (that will be yet a third state that becomes accessible via switching to this second state). What's the proper way to accomplish this?
I am using dequeueReusableCellWith so I don't want to simply cycle through every cell because some that are out of sight probably shouldn't be modified. I simply want any cell that is visible, or becomes visible, while the table view cell is in this second display mode to follow a second design rather than the first design.
The second design, for now, is being modified via a method I added to a subclass of UITableViewCell like so:
- (void) p_refreshDisplay {
if (self.editing) {
self.buttonToClearWidth.constant = 20;
self.buttonToClearLeadingWidth.constant = 20;
} else {
self.buttonToClearWidth.constant = 0;
self.buttonToClearLeadingWidth.constant = 0;
}
}
However, I'm not sure how to trigger this p_refreshDisplay for every visible (and to become visible) cell. It seems unwise to call this many times and refresh the table view. What would be the proper way to accomplish what I want to do?
You do what should be done for any table view change:
Update your data model or some flag as needed.
Either call reloadData on the table view or call reloadRowsAtIndexPaths:withRowAnimation: passing in indexPathsForVisibleRows as the list of rows to reload.
Implement cellForRowAtIndexPath to provide appropriate cells for the given data/flags.
It sounds like you should have a custom cell class that has one or more properties that can be set on the cell in cellForRowAtIndexPath so the cell can render itself properly based on the specified state.
You can achieve this by doing three things:
Establish some property that indicates the "mode" of the table, either a boolean or perhaps an enum if there are more than three states
Ensure that cellForRowAtIndexPath configures the cell appropriately based on the value of this property. This will ensure that newly displayed cells are configured correctly.
When the "mode" changes you can use the tableview's visibleCells property to update any currently visible cells:
for cell in tableview.visibleCells {
if let myCell = cell as? MyCustomCellClass {
myCell.setButtonStyle()
}
}

UICollectionView iOS 9 issue on project with RTL languages support

It seems like Apple's new feature of auto-flip interface on RTL languages cause problems when using UICollectionView.
I used constraints of type Trailing/Leading for the collection view and they switched their values, as they should, on RTL language.
The problem is that the data actually presented is of the last indexPath in the collection's data source but the UIScrollView.contentOffset.x of the first cell is 0.
A proper behaviour would have been one of the following:
Displaying the first indexPath correctly and switching the direction of the scroll (to the right) - Best option
Not flipping the UI/Constraints so the presented-data / indexPath / scrollView.contentOffset.x will be synchronised - Option that disabling the RTL support.
Presenting cell and data of the last indexPath but fixing the scrollView.contentOffset.x to represent the last cell position also.
I guess Apple might fix it sometime in the future but meanwhile we'll have to use workarounds like reversing array and/or scrolling to the last object.
I was in a similar situation and found a solution for this. If you are using swift, add the following snippet to your project, and it will make sure that the bounds.origin always follows leading edge of the collection view.
extension UICollectionViewFlowLayout {
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return true
}
}
If you are using Objective-C, just subclass the UICollectionViewLayout class, and override flipsHorizontallyInOppositeLayoutDirection, and return true. Use this subclass as the layout object of your collection view.
I am late but if you don't want to create an extension because it will affect all the collection View in our app. Simply create your own custom class ie.
class CustomLayoutForLocalization : UICollectionViewFlowLayout{
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return true
}
}
To use this class:
// your way of deciding on whether you need to apply this layout may vary depending on use of other tools like LanguageManager_iOS to handle multi-language support
if myCollectionView.effectiveUserInterfaceLayoutDirection == .rightToLeft {
let customLayout = CustomLayoutForRTL()
// if your elements are variable size use the following line
customLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
// if you want horizontal scroll (single line)
customLayout.scrollDirection = .horizontal
myCollectionView.collectionViewLayout = customLayout
}
There is one common solution for that problem that works for me, follow below steps to overcome that problem,
Give the auto layout constraint as per your requirement and then from attribute inspector change the semantic control property of the collection view to Force right-to-left from the storyboard.
Then open storyboard as source code and find for the “leading” attributes of your relevant collection view and replace that with the “left” and same for the “trailing” replace that with the “right”. Now you almost done.
now that will give you result as per your requirement.
import UIKit
extension UICollectionViewFlowLayout {
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return UIApplication.shared.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.rightToLeft
}
not pretty though simple math does the trick. (for horizontal collectionview)
- (void)switchSemanticDirection:(UISwitch*)sender {
//TEST switch the semantic direction between LTR and RTL.
if (sender.isOn) {
UIView.appearance.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
} else {
UIView.appearance.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
}
[self.myContent removeFromSuperview];
[self.view addSubview:self.myContent];
//reload your collection view to apply RTL setting programmatically
[self.list reloadData];
//position your content into the right offset after flipped RTL
self.list.contentOffset = CGPointMake(self.list.contentSize.width - self.list.contentOffset.x - self.list.bounds.size.width,
self.list.contentOffset.y);
}

Swift 2.0, UITableView: cellForRowAtIndexPath returning nil for non-visible cells

Please don't mark this as a duplicate question because I have found no suitable answer for my query.
I have a table view with cells that contain text fields. I have a button at the bottom of the screen. The number of rows is greater than the screen can display, so some cells are not in view. I want that at any point when the button is pressed, all textfields be read and the text input be processed. I am unable to do so because apparently because of the reusability of cells, cells out of view do not exist at all and cellForRowAtIndexPath for those indexPaths gives a runtime error. I have read answers that say that this simply can't be done. But I find it hard to believe that there is no workaround to this. Please help. Thanks in advance!
This definitely can't shouldn't be done (accessing cells that are off screen, or implementing workarounds to allow it), for reasons of (at least) performance and memory usage.
Having said that there is, as you put it, a workaround. What you will need to do it change how you are storing the values in those text fields. Rather than iterating through cells and retrieving the text directly from the text fields you should store the text for each field in an collection.
Define a dictionary property on your table view controller / data source to hold the text for each row.
Act as the delegate of UITextField and assign as such in tableView:cellForRowAtIndexPath:
Implement textField:didEndEditing: (or whatever is appropriate for your use case) and store the text in the dictionary keyed against the index path relating to the cell that contains that text field.
Update the button action method to use this dictionary instead of iterating through cells.
Create a UITableViewCell subclass, add your tableViewCells a index property and introduce a delegate like:
protocol CustomCellDelegate {
func didEditTextField(test : String, atIndex : Int)
}
Create a delegate variable on your UITableViewCell subclass.
var delegate : CustomCellDelegate?
Implement the UITextViewDelegate in your tableViewCell and set the cell to be the delegate of your textfield.
Implement these two methods:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField : UITextField) {
// call back with the delegate here
// delegate?.didEditTextField(textfield.text, atIndex: self.index)
}
So when the user ends editing the textField, your cell will call out with a delegate, sending the text and the index.
On your viewController, when you init your tableViewCell, set the delegate of the cell to the viewController, and set the index to indexPath.row .
Set up on your viewController a String array with as many items as many tableViewCells you got. Init the array with empty strings.
So you got the delegate on your viewController, and whenever it is called, you just insert the returned text to right index in the string array on your viewcontroller.
What do think, will this work?
If we can assume that cells that have NEVER been created has no text inputs, then create an Array or Set ourselves and clone the content/input texts there whenever user inputs to a cell's textfield.
Whenever, that button is clicked, you can iterate the array instead of cells to get all the inputs.
This is a bit hacky though..

Resources