{ "id": "108454", "key": "TIMOB-12399", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "15703", "description": "2013 Sprint 22", "name": "2013 Sprint 22", "archived": true, "released": true, "releaseDate": "2013-11-01" }, { "id": "15704", "description": "2013 Sprint 22 API", "name": "2013 Sprint 22 API", "archived": true, "released": true, "releaseDate": "2013-11-01" }, { "id": "14982", "description": "Release 3.2.0", "name": "Release 3.2.0", "archived": false, "released": true, "releaseDate": "2013-12-19" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2013-10-24T17:27:27.000+0000", "created": "2013-01-23T22:05:16.000+0000", "priority": { "name": "High", "id": "2" }, "labels": [], "versions": [ { "id": "13505", "description": "Release 3.0.0", "name": "Release 3.0.0", "archived": true, "released": true, "releaseDate": "2012-12-14" } ], "issuelinks": [ { "id": "25045", "type": { "id": "10020", "name": "Depends", "inward": "is dependent of", "outward": "depends on" }, "inwardIssue": { "id": "108459", "key": "ALOY-485", "fields": { "summary": "Collection binding on views for iOS incorrectly repopulating UI", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "High", "id": "2" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } }, { "id": "25043", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "108400", "key": "ALOY-482", "fields": { "summary": "View-based collection binding not properly clearing children before repopulating", "status": { "description": "A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.", "name": "Resolved", "id": "5", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "High", "id": "2" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "updated": "2013-10-25T00:55:30.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10206", "name": "iOS", "description": "iOS Platform" } ], "description": "h2. problem\r\n\r\nThere's no reliable way to know when you remove all the children from a view if the children are actually gone from the view hierarchy in iOS. The remove() call in JS executes synchronously, but behind the scenes in iOS, the actual removal of the elements from UI appears to happen asynchronously. Take this sample code:\r\n\r\n{code:javascript}\r\nvar win = Ti.UI.createWindow({\r\n\tbackgroundColor: '#fff',\r\n\tfullscreen: false,\r\n\texitOnClose: true\r\n});\r\nwin.open();\r\n\r\n// add a stack of views\r\nfor (var i = 0; i < 25; i++) {\r\n\twin.add(Ti.UI.createView());\t\r\n}\r\n\r\n// timeout is necessary since the add() appears to be async too\r\nsetTimeout(function() {\r\n\t// remove all, showing the child number\r\n\twhile(win.children.length) {\r\n\t\twin.remove(win.children[0]);\r\n\t\tTi.API.info('kids left: ' + win.children.length);\r\n\t}\r\n}, 2000);\r\n{code}\r\n\r\nThe output will vary, but often times you will see that the number of children on the {{win}} has not been reduced yet, because the child hasn't actually yet been removed from the {{win}} on the native side. Here's a sample output, notice the staggering of the decrementing around 23 and 10 due to the asynchronous removal:\r\n\r\n{code}\r\n[INFO] : \b\b \bkids left: 25\r\n[INFO] : \b\b \bkids left: 23\r\n[INFO] : \b\b \bkids left: 23\r\n[INFO] : \b\b \bkids left: 21\r\n[INFO] : \b\b \bkids left: 20\r\n[INFO] : \b\b \bkids left: 19\r\n[INFO] : \b\b \bkids left: 18\r\n[INFO] : \b\b \bkids left: 17\r\n[INFO] : \b\b \bkids left: 16\r\n[INFO] : \b\b \bkids left: 15\r\n[INFO] : \b\b \bkids left: 14\r\n[INFO] : \b\b \bkids left: 13\r\n[INFO] : \b\b \bkids left: 12\r\n[INFO] : \b\b \bkids left: 11\r\n[INFO] : \b\b \bkids left: 10\r\n[INFO] : \b\b \bkids left: 10\r\n[INFO] : \b\b \bkids left: 8\r\n[INFO] : \b\b \bkids left: 7\r\n[INFO] : \b\b \bkids left: 6\r\n[INFO] : \b\b \bkids left: 5\r\n[INFO] : \b\b \bkids left: 4\r\n[INFO] : \b\b \bkids left: 3\r\n[INFO] : \b\b \bkids left: 2\r\n[INFO] : \b\b \bkids left: 1\r\n[INFO] : \b\b \bkids left: 0\r\n{code}\r\n\r\nIt's worth noting that a safer way to remove these children would be in reverse order, as it won't be necessary for the index to change: \r\n\r\n{code:javascript}\r\nfor (var i = win.children.length-1; i >= 0; i--) {\r\n\twin.remove(win.children[i]);\r\n}\r\n{code}\r\n\r\nThis doesn't solve the problem, though, of not knowing when these operations complete. If I was to remove 100 views from {{win}} and then add a bunch of new ones to it right after, chances are that some of those views will be added before all of the prior views were actually removed, causing some seriously weird runtime behavior. I've seen this in practice implementing data binding in Alloy. When a Collection of data changes, I need to re-render a section of the UI, removing all its views, and replacing them with the updated data. This is where this problem surfaces constantly for me. I'm sure there are many other use cases.\r\n\r\nIt is also worth nothing that this problem does not occur on Android. Android appears to process these remove and add calls synchronously making for a simple, safe \"remove all\" operation. \r\n\r\nh2. proposed solution\r\n\r\n* Make the remove() and add() calls synchronous. I'm going to assume this isn't going to be possible in concern for performance. That said, it is a parity issue with Android as the remove() API behaves differently on the separate platforms.\r\n* Provide some means of knowing when the remove() call is actually complete, perhaps a callback that can fire when the operation is done.\r\n* A new API, a synchronous setChildren() function that can take an array of child elements. Not only would this allow you to safely remove all elements then add more directly after, but it would also increase overall performance by allowing you to make fewer jumps into native-land when establishing a view hierarchy.\r\n\r\nAll of these are off the top of my head, or based on a conversation with Blain Hamon. I'm open to anything that allows me to empty a view of all children and know when that operation is complete, preferably in a cross-platform manner.", "attachment": [], "flagged": false, "summary": "iOS: No reliable way to remove all children from a proxy, and know when they are gone", "creator": { "name": "tlukasavage", "key": "tlukasavage", "displayName": "Tony Lukasavage", "active": true, "timeZone": "America/Los_Angeles" }, "subtasks": [], "reporter": { "name": "tlukasavage", "key": "tlukasavage", "displayName": "Tony Lukasavage", "active": true, "timeZone": "America/Los_Angeles" }, "environment": "iOS 6.0\r\nTiSDK 3.0.0.GA", "comment": { "comments": [ { "id": "235603", "author": { "name": "blainhamon", "key": "blainhamon", "displayName": "Blain Hamon", "active": true, "timeZone": "America/Los_Angeles" }, "body": "For those reading along, win.children is expensive and would actually starve the asynch remove from working:\n\nvar mychildren = win.children;\nfor (var i = mychildren.length-1; i >= 0; i--) {\n win.remove(mychildren[i]);\n}\n\nFor testing purposes, use the description's code, but in practice, for performance reasons, caching (on iOS, reading causes a copy) is very helpful.\n\nAnyways, other possible solutions:\n# Implement setChildren as a synchronous command\n# Implement remove as synchronous and also accepting an array\n# document and make synchronous removeAllChildren\n\nPersonally proposing #1 and #2.", "updateAuthor": { "name": "blainhamon", "key": "blainhamon", "displayName": "Blain Hamon", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-01-23T22:40:19.000+0000", "updated": "2013-01-23T22:40:19.000+0000" }, { "id": "235707", "author": { "name": "tlukasavage", "key": "tlukasavage", "displayName": "Tony Lukasavage", "active": true, "timeZone": "America/Los_Angeles" }, "body": "#2 would solve my problems.\n\nI'm gonna have to wait a hell of a lot longer for #1 since it will create a parity issue and all platforms would need to implement it, but I would love that interface. I would particularly love it if it would allow an entire view hierarchy to be established. And don't establish just establish the view hierarchy from existing proxies, but let me build the initial proxies in the call so all of it can be pushed to the native side in a single call. This is a off-the-top-of-my-head-no-concern-for-details snippet:\n\n{code:javascript}\nvar win = Ti.UI.create('Window', {\n // window properties\n children: [\n { 'Label', { /* label properties */} },\n { 'View' , {\n // view properties \n children: [\n { 'Label', { /* label properties */} },\n { 'Button', { /* button properties */} },\n { 'Button', { /* button properties */} }\n ]\n }\n ]\n});\n{code}\n\nCrazy improvement in UI construction performance if all of that can be passed in one call and executed on the native side. Granted, I'd have to rewrite all the UI construction code generation in Alloy to support it, but so be it with the speed increase this would likely provide. \n\nIf #2 is pursued, it should likely be created in its own ticket as it would be a pretty huge undertaking all platforms would need to support.", "updateAuthor": { "name": "tlukasavage", "key": "tlukasavage", "displayName": "Tony Lukasavage", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-01-24T14:54:57.000+0000", "updated": "2013-01-24T14:54:57.000+0000" }, { "id": "235867", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "@Tony - in last example, how would you do anything with children of some UI element (Button for example)? Like add event listeners, update it after rendering, etc.\r\n\r\n\r\n***\r\n\r\n\r\nCould performance be better if array of elements were passed to particular methods (like \"add\", \"remove\")?", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2013-01-25T12:01:05.000+0000", "updated": "2013-01-25T12:01:05.000+0000" }, { "id": "244516", "author": { "name": "dyerrington", "key": "dyerrington", "displayName": "David Yerrington", "active": true, "timeZone": "America/Los_Angeles" }, "body": "With a few projects I'm involved with that essentially build UI elements dynamically from data definitions, Tony Lukasavage's proposed implementation could be very useful. It would also be nice if there could be a mapping of event listeners and methods to UI elements in the same vein as \"off-the-top-of-my-head-no-concern-for-details\" ;)", "updateAuthor": { "name": "dyerrington", "key": "dyerrington", "displayName": "David Yerrington", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-03-28T18:59:59.000+0000", "updated": "2013-03-28T18:59:59.000+0000" }, { "id": "276466", "author": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Pull pending against master\r\nhttps://github.com/appcelerator/titanium_mobile/pull/4838", "updateAuthor": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2013-10-23T21:53:16.000+0000", "updated": "2013-10-23T21:53:16.000+0000" }, { "id": "276722", "author": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Closing ticket as fixed. While using the description test code, was able to verify there were no duplicate kids in the console.\n\nTested on:\n\nSDK build: 3.2.0.v20131024120843 \nTi CLI: 3.2.0 (72f7426b4ee6c2d2883c666d5b7e03906a16012f)\nDevices: iphone 5 (6.0), ipad 2 (7.0.2)", "updateAuthor": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2013-10-25T00:55:22.000+0000", "updated": "2013-10-25T00:55:22.000+0000" } ], "maxResults": 7, "total": 7, "startAt": 0 } } }