Data Tree for iOS - an Update
Sunday, May 08, 2011
These last few months, I've been working on an update to Data Tree for iOS users. Today, I finally wrote the last line of code that makes it all work.
This has been one hell of an experience. Hopefully, you can tell from the original release that when it comes to recreating the look, feel and experience of a native control, I aim for perfection and nothing short of perfection here would do. Well, you don't want Apple to reject your applications because there was a scrollbar, do you?
Originally when I started to work on adding iOS functionality, I was hopeful that I could simply build upon the existing engines, routines and managers and it wouldn't take too long to complete. Unfortunately, it didn't work out to be that simple and I ended up having to write many, many lines of code to get things to look and work just right.
The real kicker is the relationship between touch events and scrolling events and I thought it would be really helpful to other developers out there if I wrote about my experience.
When does a tapping interaction become a scrolling interaction?
Oh, how much fun was this? in a desktop UI, these are very much separate: you can click on nodes or interact with the scrollbar. In iOS, there is no scrollbar. One touch has to do it all.
When, like me, you want to recreate a control as if it were a native one, you have to dissect what the OS does and then work out how to apply it to a control that Apple doesn't actually give Cocoa developers.
For example, say the user taps a node in the tree, highlights it and then begins to scroll. What happens? In iOS, the node unhighlights and after the scrolling has been done, the node that was previously highlighted is re-highlighted.
But, if the user taps and scrolls immediately, the highlight stays put and the list scrolls.
A quick tap on its own has to activate the node but not if the user taps on a toggle triangle. Also, it would be very un-Applelike if tapping a triangle immediately activated it.
The touch handlers have to cater for all of this, whilst the scroller control has a mind of its own.
Speaking of scrolling controls
In implementing the iOS scrolling controls, I was slightly surprised to find that RunRev requires you, the developer, to create and place them yourself at runtime. That does go a bit against the idea behind LiveCode and actually introduces a code-build-test cycle should you want to evaluate how the fruits of your labours are working.
I can see why, though. Although deploying a scroll control is quite easy, fine tuning it to work the way you would expect it to has some complication and with all of the uniqueness of iOS, you can't really do that within the desktop environment. It does, however, make things a little bit more complicated for the new user that wants to write software for iPads like we used to back in the 80s and 90s with HyperCard. But I digress.
Quirks in LiveCode
Now for the hair pulling moments. Although the language and syntax in the iOS implementation of LiveCode is pretty robust, I have come across a few oddities.
The first came up when working with the iOS scroller controls. As these controls sit on top of a group, naturally, you'd want to scroll the underlying group in realtime. What I found here is that to make anything happen, it wasn't enough to refer in code to a group by its id. I also had to refer to the stack that owns it.
What makes it all the more bizarre is that none of the example stacks that come with LiveCode have to do this. Also, when I tried to recreate the situation in a test stack, it all worked. I debugged, debugged again, pulled my hair out and went on to evaluate every line of code in the scrollerBeginScroll and scrollerDidScroll events as they executed. And then again with everything they called, but I just simply couldn't get to the bottom of why I had to be so specific.
Another quirk seems to be related. I couldn't get some aspects of the tree to draw when these routines were called inside of a handler. Once the handler finished, changes would be made, so they were being executed. Obviously, I needed them to happen at that precise moment and in the end, the solution was to call the routine by sending the message in time (tricks such as waiting for 0 seconds or locking and unlocking the screen didn't help one jot).
Ho hum. So, if you find that you can't get or set properties or modify the appearance of controls during a scrollerDidScroll event, try addressing it by its id and the stack that owns it (or just the topStack, if you don't know the stack's name or id). If you really can't get the appearance to change, try making those changes in a handler that's called using the send in time command.
Finally, the most frustrating issue I came across that I couldn't rely upon the name of the control that was placed in both the mouseControl or in the target, when scroller events were being executed. When a scroll group sits on top of another control, there's a really fine line between an event being sent to the underlying control, and thus filling the mouseControl with the name of an object, and immediately activating the scroller control, which returns the name or id of the card. If your scrollerDidScroll message looks to evaluate the mouseControl or the target to see what's underneath, it won't work all of the time.
Thankfully, each time a scroll event happens, it's possible to test the id of the control that's been interacted with. So, to overcome the mouseControl/target problem, the ids of all of the scroll controls that sat on top of Data Trees were cached upon their creation and by looking up the ID of the scroller being interacted with in the cache, the correct tree could be chosen and scrolled.
Last piece of advice
Having gotten to the end of the coding effort, my final piece of advice is to abandon mouse events altogether and go for touch events. But that story I shall save for another day.
So the code is all done and I've tested it best I can with the simulator. All that's left to do is to update the manual, create a demo stack or two and I can put it all out for public testing on real life iOS devices.
So, in summary of all of that: almost there!