Programming Notes
nacho4d avatar

Programming Notes

@nacho4d

scrollViewDidEndScrolling ?

Last time I tried this I think it was in iOS2.x, 3.x ... so instead of looking for an old code I decided to implement this again. I want to know when the text view did end scrolling. I mean when the text end moving (regardless regular dragging or drag + deceleration).


First I created my regular UITextView, set the delegate and implemented the delegate methods:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    if (scrollView.isDecelerating) {
    NSLog(@" ... restarting");
    } else {
    NSLog(@"Scroll will begin");
    }
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(CGPoint *)targetContentOffset
{
    NSLog(@"will end dragging: velocity: %f", velocity.y);
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (decelerate) {
    } else {
        NSLog(@"Scroll did end (Dragging)");
    }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSLog(@"Scroll did end (Decelerating)");
}

So if I ...

scroll without deceleration I get:

Scroll will begin
will end dragging: velocity: 0.000000
Scroll did end (Dragging)


scroll with deceleration I get:

Scroll will begin
will end dragging: velocity: 0.321089
Scroll did end (Decelerating)


start scrolling with deceleration and while its decelerating do another scrolling without deceleration I get:

Scroll will begin
will end dragging: velocity: 1.094134
... restarting
will end dragging: velocity: 0.000000
Scroll did end (Dragging)


start scrolling with desceleration and while its decelerating do another scrolling with deceleration I get:

Scroll will begin
will end dragging: velocity: 1.995404
... restarting
will end dragging: velocity: 0.442658
Scroll did end (Decelerating)


Everything goes as expected :)


Why do I need scrollViewWillEndDragging:withVelocity:targetContentOffset: ?. This is my little finding: If that method is **not** implement and then I ...

start scrolling with deceleration and while its decelerating do another scrolling without deceleration I get :

Scroll will begin
will end dragging: velocity: 1.094134
... restarting
will end dragging: velocity: 0.000000
Scroll did end (Dragging)
Scroll did end (Decelerating)

Huh?? The textview ended dragging and just after decelerating? Did I find a bug?
So, in order to get notified only once when the text did end scrolling I need to implement scrollViewWillEndDragging:withVelocity:targetContentOffset: ?? ??
I hope I am not missing something.


In the mean time I uploaded a sample project that show the issue here.

Installing Node.js for the third time

I think this is the third time I want to use node.js in a different computer and I **always** get stuck in the same place.
I know there are several "How do I install node.js" tutorials, blogs, etc.
Here is what it worked for me:

  • In theory you install node using brew: brew install node (or brew upgrade node to upgrade current node installation) and then npm : curl http://npmjs.org/install.sh | sh.
    curl https://npmjs.org/install.sh | sh

  • But in practice, At least me, I always get permission errors, link errors, it looks pretty bad. It is because I need to set adjust my permissions first : sudo chown -R $USER /usr/local.

  • Then, If you got: "Error: The linking step did not complete successfully." finish node installation with brew link node.

  • Now installing npm should work flawless too: curl http://npmjs.org/install.sh | sh

Hope it helps... :)

Other References

Adding Linkedin Badge to Blogger

I write this here so If I ever decide to change the template of this blog I have a reference to go back and set this again :)

Basically I simply replaced :
<a class='profile-link' expr:href='data:userUrl' rel='author'><data:viewProfileMsg/></a>

with Linkedin's badge. Something like this :)
 <b:if cond='data:aboutme != ""'><dd class='profile-textblock'><data:aboutme/></dd></b:if>
 </dl>
   <a href='http://jp.linkedin.com/pub/guillermo-ignacio-enriquez-gutierrez/31/b53/173'><img id='linkedinImg' border='0' src='http://www.linkedin.com/img/webpromo/btn_myprofile_160x33.png'/></a>
   <!-- <a class='profile-link' expr:href='data:userUrl' rel='author'><data:viewProfileMsg/></a> -->
 </b:if>

optionally, remove the border and shadow of img tag. (Put this somewhere before ]]></b:skin>)
#linkedinImg{
-webkit-box-shadow: none; 
-moz-box-shadow: none; 
box-shadow: none;
border: 0px;
}

In my opinion this looks much better than Google+ :)


Catching Keyboard Events in iOS


Some notes before starting ...

Things explained here are NOT possible using public APIs so this definitely violates AppStore's rules. However, if you want to take your chances and implement this for the AppStore or perhaps for Jailbroken iPhones ... keep on reading :) I think this time it is harder to get caught since this doesn't need to link against private frameworks or add private headers, etc.

The question is: How?

The short answer

The trick is in accessing GSEventKey struct memory directly and check certain bytes to know the keycode and flags of the key pressed. Below code is almost self explanatory and should be put in your UIApplication subclass.

#define GSEVENT_TYPE 2
#define GSEVENT_FLAGS 12
#define GSEVENTKEY_KEYCODE 15
#define GSEVENT_TYPE_KEYUP 11

NSString *const GSEventKeyUpNotification = @"GSEventKeyUpHackNotification";

- (void)sendEvent:(UIEvent *)event
{
    [super sendEvent:event];

    if ([event respondsToSelector:@selector(_gsEvent)]) {

        // Key events come in form of UIInternalEvents.
        // They contain a GSEvent object which contains 
        // a GSEventRecord among other things

        int *eventMem;
        eventMem = (int *)[event performSelector:@selector(_gsEvent)];
        if (eventMem) {

            // So far we got a GSEvent :)
            
            int eventType = eventMem[GSEVENT_TYPE];
            if (eventType == GSEVENT_TYPE_KEYUP) {
                
                // Now we got a GSEventKey!
                
                // Read flags from GSEvent
                int eventFlags = eventMem[GSEVENT_FLAGS];
                if (eventFlags) { 

                    // This example post notifications only when 
                    // pressed key has Shift, Ctrl, Cmd or Alt flags

                    // Read keycode from GSEventKey
                    int tmp = eventMem[GSEVENTKEY_KEYCODE];
                    UniChar *keycode = (UniChar *)&tmp;

                    // Post notification
                    NSDictionary *inf;
                    inf = [[NSDictionary alloc] initWithObjectsAndKeys:
                      [NSNumber numberWithShort:keycode[0]],
                      @"keycode",
                      [NSNumber numberWithInt:eventFlags], 
                      @"eventFlags",
                      nil];
                    [[NSNotificationCenter defaultCenter] 
                        postNotificationName:GSEventKeyUpNotification 
                                      object:nil
                                    userInfo:userInfo];
                }
            }
        }
    }
}


If you are asking yourself "Where all those #defines come from?", "Application subclass?, I don't have such a thing in code" then please read the long answer, I hope you find if helpful :)

The long answer:

UIEvents are simple wrappers of GSEventRefs, which contain a GSEventRecord struct in it. Usually we only treat UIEvents representing touch events because the UIApplication will not dispatch other events to our views or objects we create. These are UIInternalEvents and can represent accelerometer events, volume events, keyboard events, etc.

  1. In order to intercept all events our application receives we need to override sendEvent: method in our UIApplication subclass.
  2. We need to access the GSEvent and check it is a key event and then check its flags (Shift, Cmd, Ctrl, Alt). Its easy as that, the problem is we don't know how to get the type and flags of GSEvent, it is a private API :(

- GSEvent objects -
So I did the homework and according to Kenny TM in here and here, GSEvents looks like:
typedef struct __GSEvent {
    CFRuntimeBase _base;
    GSEventRecord record;
} GSEvent;
typedef struct __GSEvent* GSEventRef;
typedef struct GSEventRecord {
    GSEventType type; // 0x8 //2
    GSEventSubType subtype;    // 0xC //3
    CGPoint location;     // 0x10 //4
    CGPoint windowLocation;    // 0x18 //6
    int windowContextId;    // 0x20 //8
    uint64_t timestamp;    // 0x24, from mach_absolute_time //9
    GSWindowRef window;    // 0x2C //
    GSEventFlags flags;    // 0x30 //12
    unsigned senderPID;    // 0x34 //13
    CFIndex infoSize; // 0x38 //14
} GSEventRecord;
typedef struct GSEventKey {
 GSEvent _super;
 UniChar keycode, characterIgnoringModifier, character; // 0x38, 0x3A, 0x3C
 short characterSet;  // 0x3E
 Boolean isKeyRepeating; // 0x40
} GSEventKey;
Headers seems to be a bit old (iOS3~4) but things haven't changed so much. What is for sure is that keycode is right next to infoSize. (Isn't this the fun of private APIs?).
Everything we have to do now is to count bytes from the start of the memory of GSEvent
int *eventMem;
eventMem = (int *)[event performSelector:@selector(_gsEvent)];
GSEventType is at 2
GSEventFlags at 12 and
Unichar keycode at index 15.
I do some checks in the way but that is basically all. :)

Yes, Apple might change GSEvent at anytime so if you are a challenger and would like to submit this to the AppStore, at least do do this conditionally:
// I am lazy to do the proper check here in the post :)
[super sendEvent:event];
if ([[[UIDevice currentDevice] systemVersion] intValue] == 5) {
    ...
}

If you use it or tried to, it would be awesome you drop a line in the comments. (^-^)/
I still have not completed a sample for this little hack but in the mean you can check this this gist out where you can find some keycodes and masks.

References

Share buttons

Added buttons: Finally added tweet, like, share, burp and fart buttons (LOL) to this humble blog in blogger. So much info around there but not all of them are up to date, it was hard to find the correct information.
Got some info from here, here, here and here put the code inside my blog in the footer :)
Which one was the easiest ?
The winner is LinkedIn besides it looks great :) but GooglePlus was almost as easy as the winner.

Which one was the most difficult ... ?
Facebook, Ohh how I hate Facebook!

Twitter is OK :)
<div class='post-footer'>

 <!-- Tweeter -->
 <div style='float:left;padding:4px;'>
   <a class='twitter-share-button' data-count='none' data-lang='en' data-via='nacho4d' expr:data-text='data:post.title' expr:data-url='data:post.url' href='http://twitter.com/share' rel='me'/>
   <b:if cond='data:post.isFirstPost'><script src='http://platform.twitter.com/widgets.js' type='text/javascript'/></b:if>
 </div>
 <!-- Google plus -->
 <div style='float:left;padding:4px;'>
   <g:plusone annotation='none' expr:href='data:post.url' size='medium'/>
 </div>
 <!-- LinkedIn -->
 <div style='float:left;padding:4px;'>
    <b:if cond='data:post.isFirstPost'><script src='http://platform.linkedin.com/in.js' type='text/javascript'/></b:if>
    <script expr:data-url='data:post.url' type='in/share'/>
 </div>
 <!-- Facebook -->
 <div style='float:left;padding:5px;'>
   <a expr:share_url='data:post.url' name='fb_share' rel='nofollow' type='share'/>  
   <b:if cond='data:post.isFirstPost'><script src='http://static.ak.fbcdn.net/connect.php/js/FB.Share' type='text/javascript'/></b:if>
 </div>
 <br/>
 <br/>

Any kind of comments or criticism is welcomed here :)

CATimingFunction with bezier path


CoreAnimation is awesome! specially because it lets you define a timing function for you animation. But, somehow I always find myself googling for a interactive bezier curve creator site or something that lets me draw the curve.

Really, I don't know how many times I've done the same search and sometimes I can't find the page I used in the past.
So this time I wrote a simple but interactive HTML (using the canvas) that helps me define a bezier path I can use to create a custom timing function for CoreAnimation:
Here is the source:
$ git clone git@github.com:nacho4d/BezierJS.git


Versions in OSX 10.7 : Half tutorial, half just notes

Foreword
Last week I spent some days implementing one of the new features in MacOSX 10.7 Lion: Versions and autosave.
It was not so straight forward, APIs are too new that there is no samples yet. Documentation is not enough for newbies like me. (hehe)

I thought that watching Apple's session 107 of WWDC 2011 was going to be enough but it turns out that the video/pdfs dont show all the code you need. Besides I couldn't find the sample code specifically for that session!. TextEdit autosave implementation is just too simple, not very educational in this regard.

I think my case is not trivial. Why? Because I am using a NSWindow subclass with NSBorderlessWindowMask and I have to craft close/minimize/maximize buttons, handle window resizing by my own. I even have to add the autosave button by my own and I still don't get the "--Edited" or "--Locked" string right next to the versions button (NSWindowDocumentVersionsButton)

So here are my notes:
(I assume the app is a document-based cocoa application)

First:
The easy part, override +[NSDocument autosaveInPlace] which will enable versions by default
//MyDocument.h
+ (BOOL)autosavesInPlace{
 return YES;
}
Or override +[NSDocument preservesVersions] directly.

Now, without much effort you will get something like this:

Second:

Still easy, implement the following NSWindowDelegate methods. If you are using a subclass of NSWindowController that is a good place, if not then the NSDocument subclass should be fine :)

To customize the size of the window in the version browser:
//Bug? : In NSBorderlessWindowMask windows below method is called but has no effect!
- (NSSize)window:(NSWindow *)aWindow willResizeForVersionBrowserWithMaxPreferredSize:(NSSize)maxPreferredSize maxAllowedSize:(NSSize)maxAllowedSize{
    //Create a custom size for the window entering version browser
    //This is just an example of a size you might use. This is quite big
    float ratio = maxPreferredSize.height/maxPreferredSize.width;
    NSSize winSize = [aWindow frame].size;
    winSize.height = winSize.width * ratio;
    return winSize;
}

To customize the window when entering/exiting version browser
- (void)windowWillEnterVersionBrowser:(NSNotification *)notification
{

    //Suggested by the docs:
    //disable UI elements that will have no effect in version browser.
    //Simplify the UI, as well

    //Example a textView:
    MyWindow *win = (MyWindow *)[notification object];
    [win hideTitlebarAndStatusBar];     //method of MyWindow : simply the window
    [win setUserIntaractionEnabled:YES];//method of MyWindow : disable UI
    [myTextView setEditable:NO];        //disable UI
}

- (void)windowDidEnterVersionBrowser:(NSNotification *)notification
{
    //If needed, do your thing here
}
- (void)windowWillExitVersionBrowser:(NSNotification *)notification
{
    //If needed, do your thing here too
}

- (void)windowDidExitVersionBrowser:(NSNotification *)notification
{
    //Suggested by the docs
    //Set the window to its original settings

    MyWindow *win = (MyWindow *)[notification object];
    [win showTitlebarAndStatusBar];     //method of MyWindow : restore the window
    [win setUserInteractionEnabled:YES];//method of MyWindow : restore UI
    [myTextView setEditable:YES];       //restore UI
}

Third
In fact this step is easy too but not very well documented, I believe.

In normal cases (When using regular windows with non NSBorderLessWindowMask) doing step 1 and 2 is enough. Cocoa will coordinate buttons in the window and resized to not to work while in the version browser.

In case of NSBorderLessWindowMask windows we need to do more.
As suggested by Apple docs UI/input should be disabled and in the window should be simplified to show only relevant information.

Something that is not clear in the docs is that above methods are only called for the current document (The document in the left side of the version browser).
So in order to disable UI in windows of past versions's documents (the documents in the right side of the browser) we need other APIs:

-[NSDocument isInViewingMode] will return YES for all the documents in the right side of the browser. NO for the current document
And -[NSDocument windowForSheet] will return the windows for all these documents.

So something like this should work:
- (NSWindow *)windowForSheet{
    MyWindow *win = (MyWindow *)[super windowForSheet];
    MyDocument *doc = (MyDocument *)[[win windowController] document];
    if ([self isInViewingMode]) {
        //disable UI temporarily
        [win hideTitlebarAndStatusBar];
        [win setUserInteractionEnabled:NO];
     [doc->myTextView setEditable:NO];
    }else{
        [win showTitlebarAndStatusBar];
        [win setUserInteractionEnabled:YES];
        [doc->myTextView setEditable:YES];
    }
    return win;
}

However, in version browser, each document is created in a different thread, so if you are handling big documents it should be much more efficient to above check in the same a method that run in the same thread. (-[NSDocument windowForSheet] is called in current document thread)

So, we better write above content inside windowControllerDidLoadNib: to avoid thread changing. :)

BTW. If you wan't to debug your app's version browser set flag NSDocumentRevisionsDebugMode to YES. Each window/document in the version browser is loaded in one different thread so without this flag is difficult to debug


Fourth:
These are not mandatory but recommended to implement.
Enabling writing/saving asynchronously might improve dramatically application responsiveness, specially when working with big documents.

- (void)autosaveWithImplicitCancellability:(BOOL)autosavingIsImplicitlyCancellable completionHandler:(void (^)(NSError *errorOrNil))completionHandler;
- (BOOL)canAsynchronouslyWriteToURL:(NSURL *)url ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation;
- (void)continueActivityUsingBlock:(void (^)(void))block;
- (void)continueAsynchronousWorkOnMainThreadUsingBlock:(void (^)(void))block;
- (NSDocument *)duplicateAndReturnError:(NSError **)outError;
etc


Window restoration:
I wish Apple showed this part too in their examples so my life would have been easier but NO! - they like me to suffer! (Well not really...)

Implement these NSWindowDelegate methods too to save/restore some info into/from the coded window.

- (void)window:(NSWindow *)window willEncodeRestorableState:(NSCoder *)state
{
     //any additional info to save 
     //Example:
     [state encodeObject:myObj forKey:@"myObject"];
}
- (void)window:(NSWindow *)window didDecodeRestorableState:(NSCoder *)state
{
     //any additional info to restore
     //Example:
     MyObject *myObj = [state decodeObjectForKey:@"myObject"];
}

Similarly you can override below methods of NSWindow
I am saving the state of the title bar because that is not done by default by Cocoa
- (void)restoreStateWithCoder:(NSCoder *)coder
{
    [super restoreStateWithCoder:coder];
    titleBarHidden = [super decodeBoolForKey:@"titleBarHidden"];
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];
    [coder encodeBool:titleBarHidden forKey:@"titleBarHidden"];
}

Basically that is all.
More info can be found in the following links and surely everywhere very soon as Lion and these features becomes more and more popular.

References

This work is licensed under BSD Zero Clause License | nacho4d ®