Dynamic Type Static UITableViewCell - ios

I have a static UITableView with a UITableViewCell that contains a label. I have it set up to look nice right now, but when a user has their Dynamic Type turned to a larger setting, the label is cut off.
The UILabel has static ~15px constraints set up on all four edges of the UITableViewCell's content view.
How can I make the UITableViewCell's height change dynamically as the Dynamic Type adjusts the size of the UILabel's body formatted text.

Subscribe to UIContentSizeCategoryDidChangeNotification and when you get notified change your heightforRowAtIndexPath accordingly.

I know you asked this question many years ago but, as you didn't share any solution here, I guess you didn't find one.
Standard table view cells content is automatically adjusted thanks to the cell-sizing feature.
Constraints must be adapted in case of custom cells in order to obtain the desired rendering and let the cell-sizing work.
If your table view cells don't size to fit content, try and use the properties UITableViewAutomaticDimension associated to estimatedRowHeight.
Finally, check out this WWDC video detailed summary dealing with the best way to build apps with Dynamic Type if the explanations above aren't enough.

Related

What actually defines the height of a UITableViewCell?

I'm working on an iOS App right now and I want to build a view controller that uses a UITableView to create new events in a calendar (very similarly to how iOS handles event creation in the system calendar, actually). The table view has two sections, the first section holding a date picker and the second section holding two custom cells for entering an event name and notes via a text field and a text view. After playing around with them I managed to force-set them to the right size, but in the process I realized that I don't actually understand how iOS calculates individual cell heights, especially in a table view with multiple sections and multiple custom cell classes. So far, I've found a number of things that seem to play a role:
Contents of a cell, e.g. a text field and its constraints
Hugging priority and compression resistance priority of a cells content
Settings for row height and view height in the size inspector of the cell itself:
Arrangement and Autolayout settings in the size inspector of the cell
Settings for the rowHeight and estimatedRowHeight properties of a UITableViewController
The more I look into it, the more complex and confusing it all gets. Maybe one of you can shed some light on this shady bit of Swift magic?
Basically, the rule is that if the table view's rowHeight is UITableView.automaticDimension, then as long as the estimatedRowHeight isn't 0, you'll get automatic row heights, meaning that the height is determined by the cell's autolayout constraints from the inside out.
The settings can be made in respect to the table view as a whole (in code or in the storyboard) or for a single cell using the height delegate method.
Add your constraints in the cell in right way.
don't use tableview "height for cell" delegate method.
use this in your viewDidLoad
self.tableView.estimatedRowHeight = 44.0
self.tableView.rowHeight = UITableView.automaticDimension
I would say that table view has a bit tricky.
Originally it needed to know size of cell before the cell was created.
The height of cell is defined by UITableViewDelegate optional function tableView(_:heightForRowAt:)
If this function is not defined (or delegate is set to nil) then it will take value of tableView.rowHeight
For performance reasons there was also added tableView(_:estimatedHeightForRowAt:) and tableView.estimatedRowHeight
The idea was not to calculate height of every cell during fast scrolling (such calculation may be costly) and use height that is good enough.
So that are the basics before constraints layout.
Then magic came. You can return UITableView.automaticDimension as height (by delegate method or by setting tableView.rowHeight). It will force tableView to calculate height from cells' constraints (note that constraints must define that height so very likely you want to set content hugging and resistance priority of every label, and you will encounter 'errors' in storyboard/xib).
Since that operation is costly you Apple forces you to specify estimated height by yourself. Also it's important to set that value to something that makes sense, otherwise things like programatically scroll won't work correctly.

Layout changing when running the app

I've been trying for days to make one layout of my app to work well, and after days of learning and mistakes I still can't get the table cell layout to look how I want it to be.
This is how my cell .xib looks like in the editor:
http://i.stack.imgur.com/VEr3r.png
And this is how my app looks like when running with suggested constraints:
http://i.stack.imgur.com/wiK1f.png
Why is that? How I can find my mistake and make the layout like it supposed to be, in the view?
Suggested constraints are rarely what I actually wanted to see.
For each label with fixed text, lock the horizontal and vertical positions either to the view or to the next adjacent item.
For imageViews, choose the size you want and lock the height and width.
For labels that you will be dynamically changing the text on, pick a size that will hold the longest string and lock the width. You'll need a vertical position constraint.
You have many options to simplify your layout.
you can use the stakview or create a static UITableView and insert your component inside the cells and enter the Constraint. excellent tutorial http://www.runtimecrash.com/2015/09/17/exploring-uistackview/

iOS Calculate Height For Cell

I have a problem with Apple's dynamic resizing cells such that the logic for the layout of my cells isn't as cut and dry as just a few stacked growing UILabels.
As a result, I can't really use the dynamic resizing option they've provided and so I need to manually calculate the height of my cell using NSString boundingRect methods.
That's fine - it works, but I end up needing to store a lot of constants that keep track of my auto layout constraints. I feel like this is counter intuitive to what auto layout is supposed to do for me, so I'm not sure if this is the correct way to implement heightForRowAtIndexPath.
I essentially have to go and copy my constraints into a constant and then use those values in a class method to generate my heights. Apple provides very little internal insight into how UITableViewAutomaticDimension works, but it's clear that the height of the cell is still calculated BEFORE it is laid out. Thus I can't really add any complex logic to it unless I know what methods are called before.
Any ideas on what I should do, or if my approach is ok?
The common solution I can offer is to add a height constraint fro your cell and change the constraint according to your needs, whatever they are. UITableViewAutomaticDimension will resize cell to the height you specify with this constraint automatically.
If your table cell is custom then its easier if you use a method like configureCell and pass the indexPath to it from cellForRowAtIndexPath and then determine the layout based on the data that you have and see where you want the left and right label to be placed. Once you have done this store the height required in the model or perhaps another array that has the same number of rows as your table and use it to return in heightForRowAtIndexPath.
This is easier and gives you flexibility without having to fiddle with too many delegate methods of table view. Centralise your layout logic in one place.
Another alternative is to override layoutSubviews of the table view cell and calculate the height there and store it.
If you want your tableview cell to be assigned a default height, you can use estimatedHeightForRowAtIndexPath: available in UITableViewDelegate. As per Apple guidelines:
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.

xcode 5 scrollview doesnt allow me to go all the way down with out it "bouncing"

Is there a way to have each label equal distances apart depending on how many lines are displaying per table cell. I can see all the data if I use both hands to tug the info up the screen but that isnt user friendly.
It sounds like you have UILabels in UITableViewCells? You may need to adjust the height of the cells via UITableViewDelegate's tableView:heightForRowAtIndexPath: message. In there, you probably want to calculate your height using boundingRectForSize:options:attributes:context: from NSString (iOS 7 and later). Make sure the numberOfLines property in your UILabel is 0 to adjust for the expanding height, and set the appropriate constraints.

With Dynamic Type in iOS 7, how do I factor in more advanced layout issues, such as spacing between labels/views?

I'm updating my app to support Dynamic Type in iOS 7. It was relatively easy to make the text adjust its size depending on the system setting, but as I'm using it in the context of a UITableView and cells with multiple UILabels in them, text size isn't the only thing I have to worry about. If the text grows, the cell's height should as well, if the text shrinks, so should the cell height.
Also, if it gets smaller, it should obviously have less spacing between items when compared to at its largest size type (as at a small size the spaces between would be giant).
How do I change more advanced layout issues such as these when the user changes their Dynamic Type size?
Right now, I'm doing something really ugly that barely works. I look at the height of one of my labels and scale my constants with its size. But it's very imprecise, as, say 110% of a UILabel height at the current text size being used as the padding between elements will not necessarily be universally working.
So here's what I'm doing in that example:
CGRect articleTitleRect = [article.title boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.contentView.bounds) - 29, MAXFLOAT)
options:NSStringDrawingUsesFontLeading
attributes:#{ NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline] }
context:nil];
self.urlConstant.constant = articleTitleRect.size.height / 5;
self.previewConstant.constant = articleTitleRect.size.height / 5;
(Basically finding out what the height of a label is, then using percentages of that to infer the spacing. Again, very imprecise and doesn't work well universally.)
The other thing I considered doing was checking what the current preferredFontForTextStyle: is equal to at a point size, and for specific values hardcode the interface adjustments/spacing. This works a little better, but it still doesn't seem optimal to what Apple had in mind, as it's not terribly dynamic (it breaks if they add another type size, for example) and you're almost sniffing for values they don't give you off the bat (which makes it seem hacky).
So what do apps such as Tweetbot 3 (that now use Dynamic Type to set their UITableViewCell elements) do to make their UI look so well done over different Dynamic Type sizes? What's the best way to go about doing this? There honestly seems like no tutorials on the topic.
This is something that you will have to do yourself, but iOS has given you the tools you need with TextKit. There is actually a lot of documentation in the Test Programming Guide.
For example, in the Working with Font Objects section, the UIContentSizeDidChangeNotification, which informs your app that the Dynamic Type value has changed, with a userInfo dictionary with the new value. This is the entry point to what changes you want to make. For example, if the new value is UIContentSizeCategoryAccessibilityMedium, the distance between two labels is 10 points, but if the new value is UIContentSizeCategoryAccessibilityLarge, you can set it to 15. Of course I'm just making up values, figuring out what works best is something you'll have to do through trial and error. However, once you figure out the right distances, making sure everything works shouldn't take more than a dozen lines of code.
Also take a look at UIFontDescriptor, especially the constants at the bottom of that reference. They let you access pretty much every font property and trait imaginable. You can use that to "build" your own font with custom properties. If you want to go that way, it's going to require a little bit more code, but TextKit provides you with a lot of different APIs when it comes to showing text on screen.
I haven't used dynamic type yet. But I think one approach you could use would be to use auto-layout to lay out your cell content, and let the auto-layouting engine determine your required cell heights. Then, when the dynamic type size updates you'd simply have to ask the tableview to either reload or recalculate (via beginUpdates/endUpdates).
I answered a question with an example of how to use auto layout to calculate the tableview cell height for any given cell, here:
How to resize superview to fit all subviews with autolayout?
EDIT
Per your comment:
Height's not really the issue here, I can calculate that rather
easily. My question is moreso how to deal with the harder things, like
space between labels for instance, where as the labels get bigger
their spacing should grow slightly as well. Also just basically
learning how best to adjusts layouts affected by Dynamic Type
Anytime you need to adjust a constraint at runtime once the constraint has been created and registered, you do so by adjusting the constant property of the constraint. So if you want to tweak the spacing between two items based on some other property (e.g. text size) you have to do that manually by adjusting the constraint constant that manages spacing for those two items. If the constraint was created in Interface Builder you need to bind it to an IBOutlet in somewhere so you can refer to it in code.
Constraints also have a multiplier property, which you can use to dynamically adjust one constraint based on the calculated attribute value of some other view. I don't think you can manipulate this in Interface Builder, but if you create your constraints in code you can. Using the multiplier you should be able to set up a spacing constraint that adjusts larger or smaller based on the height of some other element.
In a more complex scenario, you might desire to dramatically change the layout given some property change (e.g. text size), beyond tweaking simple spacing constraints. In this case I'd recommend either of the following:
1) Create and manage your constraints entirely in code. Tear-down and build up the correct set of constraints when you determine a layout changeover is required.
2) Create multiple nibs to manage multiple UI layouts via Interface Builder-defined constraints. Dynamically load/reload the correct nib when you determine a layout changeover is required. The possibly undesired side effect of this is all of your views/controls will be recreated.

Resources