[TIMOB-13901] iOS: ListView: Cannot set dynamic height on ItemTemplate (Ti.UI.SIZE)
GitHub Issue | n/a |
---|---|
Type | Bug |
Priority | High |
Status | Closed |
Resolution | Fixed |
Resolution Date | 2013-11-11T19:19:23.000+0000 |
Affected Version/s | Release 3.1.0, Release 3.2.0 |
Fix Version/s | 2013 Sprint 23, 2013 Sprint 23 API, Release 3.2.0 |
Components | iOS |
Labels | n/a |
Reporter | Sam Schechter |
Assignee | Vishal Duggal |
Created | 2013-05-17T15:47:44.000+0000 |
Updated | 2014-09-09T09:34:03.000+0000 |
Description
*Problem*
Cannot set height of listView row to Ti.UI.SIZE, it must be a fixed height. This prevents the use of dynamic length text inside the rows without truncating the text.
*Test case*
var win = Ti.UI.createWindow({
backgroundColor : 'white'
});
var myTemplate = {
childTemplates : [{
type : 'Ti.UI.Label',
bindId : 'textLabel',
properties : {
color : '#000'
}
}],
properties : {
//height : '150dp',
height : Ti.UI.SIZE
}
}
var listView = Ti.UI.createListView({
templates : {
'template' : myTemplate
},
defaultItemTemplate : 'template'
});
var sections = [];
var dataSection = Ti.UI.createListSection({
headerTitle : 'Data'
});
sections.push(dataSection);
var items = [{
textLabel : {
text : 'This is really long text! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque molestie sollicitudin dolor quis eleifend. Maecenas ultricies sodales venenatis. Maecenas molestie purus eget sem dignissim sed tempus arcu sagittis. Aenean metus lorem, facilisis a dignissim a, tempus quis enim.'
}
}, {
textLabel : {
text : 'This is really long text! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque molestie sollicitudin dolor quis eleifend. Maecenas ultricies sodales venenatis. Maecenas molestie purus eget sem dignissim sed tempus arcu sagittis. Aenean metus lorem, facilisis a dignissim a, tempus quis enim.'
}
}, {
textLabel : {
text : 'This is really long text! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque molestie sollicitudin dolor quis eleifend. Maecenas ultricies sodales venenatis. Maecenas molestie purus eget sem dignissim sed tempus arcu sagittis. Aenean metus lorem, facilisis a dignissim a, tempus quis enim.'
}
}, {
textLabel : {
text : 'This is really long text! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque molestie sollicitudin dolor quis eleifend. Maecenas ultricies sodales venenatis. Maecenas molestie purus eget sem dignissim sed tempus arcu sagittis. Aenean metus lorem, facilisis a dignissim a, tempus quis enim.'
}
}]
dataSection.setItems(items);
listView.sections = sections;
win.add(listView);
win.open();
*Workaround*
See Community Discussion link.
Attachments
File | Date | Size |
---|---|---|
listview-android.png | 2013-05-17T17:57:15.000+0000 | 333098 |
listview-ios.png | 2013-05-17T17:57:15.000+0000 | 22860 |
Tested and confirmed on iOS 6 simulator, Ti SDK 3.1 GA and 3.2 CI. Cannot reproduce on Android (see screenshot).
Hi, any update on this? Lack of auto height on iOS kills this feature :( If there is a passable sample workaround that shows how we can calculate heights of variable length labels that go into ListItems, it would be good in the meantime.... But hopefully this issue will be addressed soon.
Here is my workaround: http://developer.appcelerator.com/question/150938/listview-with-auto-height-row http://pastie.org/7595152 No explicit height definition in item template. Item must have additional properties: autoSizeText, autoSizeMaxWidthPortrait, autoSizeMaxWidthLandscape, autoSizeMinHeight, autoSizePadding, autoSizeFontSize.
Danny - if this works, submit a pull request, and hopefully the Ti team can approve it soon and push it in 3.1.2. This is a showstopper for ListViews on iOS, IMO.
@Danny, so this works and there are no negative side effects? I need a solution for a production app, wondering if this is worth the risk since there is currently no date of expected deliver here?
I works perfect in my app ... take a look at http://www.app.me and download the iOS app. I am using it for chat messages (similar to WhatsApp) and user feeds (similar to Facebook wall), both of course with dynamic sizes. I don't know how to submit a pull request and to be honest, I have currently no time to read the faqs about it. Hope my pastie snippet can help you.
Well, if I followed Danny's code correctly, it will calculate the height, and add padding, to a single string of a specified font size. It's not a generic solution if you have multiple labels of varying font sizes, images, etc in your ListItem.
I have also templates with multiple labels and images, the calculation of the correct row height based on my pastie snippet works also fine here. You just have to extend the given height calculation routine. Maybe I can find some time in the next days to provide a general workaround.
So the July 5th due date has passed.... is anyone actively looking at this? The importance of this can't be overstated....
[~mokesmokes] The challenge (as noted by the solutions so far) is that any general solution is likely going to be slow. We can come up with solutions that work in certain narrow cases, but not generally. Our hope is that we can come up with a way for people to add a little custom code to solve it for their specific use case without needing to patch ListView itself.
Ingo, thanks for the clarification. If this is the case - can your team provide a methodology to calculate label heights in Javascript - and not Objective C? i.e. a solution that does not require modifying the SDK whenever we change our templates? It would be great if you can add to the docs an example of how to handle a long, untruncated label in a ListView (for iOS of course). Thanks!
Pull request to add an iOS Label method to calculate the height from a provided width, assuming word-wrap, accounting for the label's font. https://github.com/appcelerator/titanium_mobile/pull/4444
I'm looking for a solution that is more generic...that works for other template items besides labels. For example, my rows could have images (small ones...like glyphs...not photos) of varying sizes, plus labels. Outside of ListView, I can make a view with height:Ti.UI.SIZE that contains these things and the view will automatically size vertically as expected as I change the label text and image content. We need this same ability when rendering ListView rows. Is a ListView not just a scrolling view that contains other views (rows/sections)? Also, dynamic height works just fine on Android. Maybe the iOS team could talk to the Android team to find out how they did it?
Shawn, I looked at this for awhile. Here's my understanding. On iOS there's a selector called
heightForRowAtIndexPath
which is called for each cell when it is being displayed. This is _probably_ done before the subviews of a cell are laid out. One of the causes of the poor performance of TableView is probably that the height of the subviews must be calculated before this selector can be performed for a cell. This probably also makes it harder to reuse cells. So, if we were to do a similar auto row height on ListView it would kill the performance benefits. The reason why Danny's code works is because it is a minimal solution for his case. A generic, minimal solution would be much more complicated. Here are some solutions I can think of: 1. Do it all on the native side. The code would have to make a bunch of inferences to determine which parts of view hierarchy are actually variable height; then, it'd have to calculate the fixed portion once and add it to the variable portion for each row. This solution is very hard to implement. 2. Call a user provided callback function forheightForRowAtIndexPath
. This is infeasible because a bridge traversal for every row would be very expensive. 3. Calculate the heights in JS for each row before adding the data to the table. This would require some JS ttf magic. It's not too hard to do, and may even be reasonably fast. It would at the very least avoid stutters while scrolling the table. 4. Hybrid of the above. Basically, you'd pass a fixed width and then an array of bindIds for which the view's height needs to be calculated and added to the fixed portion. For example you'd specify in the template a view with say three label subviews, the last of which is going to have some variable length text; and you'd set fixedWidth: 40 and autoHeightBindIds: ['last_label_bind_id'] or something. Then, the implementation forheightforRowAtIndexPath
would add your fixed ID to the calculated height of the specified views for those bind IDs. It's an ugly, hacky API but it'd work, and it's a minimal solution. It also splits the complexity required to calculate heights fairly evenly between the platform and the users of the platform. And Android likely works completely differently, so asking the Android engineers isn't really an option.Having now worked with ListViews for a few weeks, here's my take on it: 1. It's easy to calculate Label heights if you have a helper function - I'm using the code I noted in my earlier comment. The Appcelerator team can accept it, or write something similar, but it's impossible to flexibly use ListViews on iOS without something like this. 2. For those willing to take the penalty hit - perhaps there can be an option for the ListView code to do auto-height on iOS. However - this should not hurt the performance of apps that calculate/know the row height. 3. I don't think images are really an issue, since image dimensions can be constrained. Is it really a realistic scenario that we do not know image size in advance, or that we don't know the size to constrain them to? So IMO labels are the real issue, and both Danny & I provided sample code to handle that in Objective C - it should be part of the Titanium API.
I'm going to push for option 1, although as a separate module instead of something core for the following reasons:
3.1.2's already code frozen, and so it's too late to get a feature in for this release. By having it as a module, you wouldn't have to wait to another release.
This is something useful outside of Label, as it has utility for layouts with textfields and textviews and the like.
I could have sworn we had something that listed the system-installed fonts. If not, a font module would be useful in listing all the options.
Fonts in general will need to have specific support as proper citizens, due to iOS 7's features that I don't know if I can mention them yet or not, and this would be the place to put it.
I like modules.
The big reason that Ti.UI.Size was left out of the listview spec is that it brings up a LOT of edge cases that got hairy quickly. So it was set aside to stew upon it while the API settled before we threw in a lot of tableview features and painted ourselves into the same corner.Thanks Blain. Not sure why the differentiation between system-installed fonts and custom fonts - but I'm using custom fonts, including Font Awesome icons - with no issues, with the code I wrote. Would your module have similar capabilities?
@Blain, @Ingo: Let me explain how i solved that problem I managed to have SIZE working with ListView. But first to understand why it works well, a few points you should know about my version of the sdk: - As on Android, a TiViewProxy on iOS doesnt create the TiUIView instantly, its only created when necessary - my TiUILabelProxy can compute size without the view being created or attached. Now this is what i did: - When you set the templates in the ListView, i create a dictionary of TiUIListItemProxy. Each one represents the template proxy (with children). I call those proxies "fake" are they are never attached, their view is never created!!! - here is my heightForRowAtIndexPath:
The final thing you need to know is that TiUILabelProxy have methods contentWidthForWidth and contentHeightForWidth that can return a result even if the view is nil. I didnt have the same thing done for ImageView yet as i am not sure i want to load the image everytime, seems heavy! Also improvement can also be made to actually look if the data is different from the one used on the last computation. Now about all that, i cant create a PR, my sdk is a lot too far from the master branch. As usual if you want to work with me, you know who i am ;)
As a high priority bug and 23 watches can I make a call that this be kept in line for work released in 3.2.0 please. It is a real issue that affects uptake of the listview component. Of course we could go back to tableview, but there will be countless man hours wasted refactoring back and forth. Thanks
We will attempt to fix this for 3.2.0, but there likely will be some caveats as a result.
Thanks Ingo! Will the caveats end up in the documentation (if so where) or will we need to read the comments in this bug to find the caveats?
I'm sure they will end up in documentation. That said, I really can't promise anything yet. The issue is how to fix this without impacting performance. A brute force approach will work, but then we get back to the issues that some people experience with TableView.
Thank you Ingo. With regards the implementation I think that something that allows label sizing would cover a lot of use cases, but so long as the featured can be toggled then people can choose to take the performance hit or not.
@Ingo: the method i use and explained before does not have hard consequences of performances. I am using it with great perfs! Just so you know ;)
I also just implemented a method to calculate label heights given the text, width and font. So we need *some* tool to enable variable heights, something is better than nothing, and we cannot go back to table view performance.
Henry, this issue is a problem on iOS only. To calculate label heights just use this: https://github.com/appcelerator/titanium_mobile/pull/4444
Thanks Mark. I don't know how to respond offline and I'm having some problems (apology in advance). I updated the CLI by running "npm install -g git://github.com/appcelerator/titanium.git". Then I got the latest SDK and added your changes. I compiled the SDK using scons. I installed the SDK using Help > Install Titanium SDK... I get the following error in the SDK when compiling my project for iOS 7 iPhone Retina. /usr/local/share/npm/lib/node_modules/titanium/node_modules/longjohn/dist/longjohn.js:184 throw e; ^ TypeError: Cannot read property 'run' of undefined at Object.
Pull pending against master https://github.com/appcelerator/titanium_mobile/pull/4939
Vishal - can you please explain a bit about your fix? What's the usage syntax and the performance penalty? Henry - I too have not successfully run 3.2.0, so won't bother with that until Appcelerator has a production release. In the meantime - just take my code and plop it into the right file in 3.1.x. No need to build for iOS - just clean your project. Thanks.
[~mokesmokes] What issue do you currently have with running 3.2.0?
Mark - Thanks again. I had never tried modifying the SDK directly. I had erroneously assumed that these files were compiled into a framework or dyld outside the process of compiling my project. I'm learning on the fly here. The fix works great and I submitted a new build of my app to the app store.
Hi Ingo - the CLI used with 3.1.3 doesn't work with 3.2.0. I tried to install the 3.2.0 CLI - that didn't work either (about 2 weeks ago). I didn't pursue it any further since I didn't want to mess with my development environment too much.
Pull request form [~vduggal] has been merged into master. This will be available on the CI builds later today.
Hi guys - can we please get an explanation, doc update, etc? I'm also noting this comment in the pull request: "Note this functionality is not guaranteed to work in templates which load remote images." What is the usage like? What are the caveats? What is the performance hit? And if this doesn't work in all cases - then we still need a fallback method to enable (at the very least) label height calculations, as in my PR. Thanks.
[~mokesmokes] Essentially we have added support for specifying height explicitly as Ti.UI.SIZE in custom templates. It should work for majority of the cases. The reasons it might not work for autosized remote images is because we have no idea of the size of the image resource until it is available in the cache. If it is already in the cache auto height will work. There is a performance hit, because now the height of the row will depend on the dimensions of the tableview being used. Autosized labels will work.
Thanks Vishal. A couple of follow-ups: 1. I assume that if I specify the height and width of the custom template's ImageView with the remote URL that Ti.UI.SIZE on the ListItem will work. Is this correct? 2. If I compare Ti.UI.SIZE on the ListItem to the method of just calculating in advance label heights as I did in my PR (in most cases it's just the variable text length that's the problem) - how much of a performance hit does your method incur? I'm asking this in the case it may make sense to allow the developer to calculate labels instead of using Ti.UI.SIZE - i.e. just add a label height calculation to the iOS SDK (my PR). Thanks.
[~mokesmokes] 1. Yes you are right. If the imageView has its width and height defined (in which case it is not autosizing), Ti.UI.SIZE for list item height should work even if the images are uncached remote images. 2. Regarding your PR about pre-calculating label heights on the proxy side, your PR will not work for Attributed Strings which specify multiple fonts for different sections of the text. In this case you need to go to the main thread and ask the view for SIZE that fits a given constraint. We are already doing that in our implementation of the Label Proxy. Note that the performance hit only happens when the size of the list view changes. So initial rendering of the list view will be delayed by a few milli seconds.
Verified with test Environment: Appcelerator Studio: 3.2.0.201311122338 SDK:3.2.0.v20131113183932 alloy: 1.3.0 acs: 1.0.7 npm: 1.3.2 titanium: 3.2.0 titanium-code-processor: 1.1.0 Xcode:5.0.2 Device: Iphone 5s(v7.0.2), Google Nexus7(v4.3) OS: Mac OSX 10.9 Item Template(Ti.UI.SIZE) dynamic height being set properly. Hence Closing as working as expected.
Reproduced on Host OS: Mac OS X OS Version: 10.9.2 Titanium Studio Version: 3.3.0.201407100905 Titanium SDK Version: 3.3.0.GA XCode Version: 5.0.2 acs@1.0.15 npm@1.4.3 alloy@1.3.1