Bad news first

We've had some trouble with the methods I added to NSDate: time in particular is in conflict with some other method - probably and internal private method. the time method returned a 3-item array with the hour, minute, and second. the other two methods that are also getting updated are date and datetime. the new names:

time      =>  time_array
date      =>  date_array
datetime  =>  datetime_array

Besides that, these methods were misleading. Does time return a Time object? Now it is clearer that an Array is returned.

If you are using these methods... sorry! All I can say is s/\.(date|time|datetime)\b/.$1_array/

Other than that, what's new?

Since the last announcement (v0.8.5), here's the new stuff.

NSDate additions, especially delta, which was damn hard to write

If you've been keeping up with the git repo, you might have seen the NSDate/Time additions. I think the start_of_day and end_of_day methods are worth pointing out, because they make it easy to check for "today's events".

now = Time.new
from_time = now.start_of_day
until_time = now.end_of_day

if from_time <= event and event < until_time
  dates << event
end

If you're building a calendar, you might find the days_in_month and days_in_year methods helpful. Takes into account leap years. Speaking of which, date.leap_year? returns true if the year of the date is a leap year (e.g. any date in 2012 will return true).

What is delta and why aren't you talking about it!?

I think NSDate#delta is really cool. It was awful to write, and I need to make tests for it (I need tests for ALL of sugarcube! For shame! I could use help on this, actually).

From the README:

(main)> feb_28_2012 = Time.at(1330473600)
=> 2012-02-28 17:00:00 -0700

# add an hour or two
(main)> feb_28_2012.delta(hours:1)
=> 2012-02-28 18:00:00 -0700
(main)> feb_28_2012.delta(hours:2)
=> 2012-02-28 19:00:00 -0700

# add some days
(main)> feb_28_2012.delta(days:1)
=> 2012-02-29 17:00:00 -0700
(main)> feb_28_2012.delta(days:2)
=> 2012-03-01 17:00:00 -0700

# how about a month?
(main)> feb_28_2012.delta(months:1)
=> 2012-03-28 17:00:00 -0600  # look, the time didn't change, event though there was a DST change!

# cool, but if you want a more literal "24 hours", specify a time unit
(main)> feb_28_2012.delta(months:1, hours:0)
=> 2012-03-28 18:00:00 -0600  # disable the DST fix by specifying hours, minutes, or seconds (a "precise" delta)

# in one year, it will still be Feb 28th
(main)> feb_28_2012.delta(years:1)
=> 2013-02-28 17:00:00 -0700

# and we already know what adding a day looks like
(main)> feb_28_2012.delta(days:1)
=> 2012-02-29 17:00:00 -0700

# a year and a day is tricky, because do we add a day, then a year?  or add a
# year and then a day?  well, i'll tell you, **I** add a day and then a year,
# which is feb 29th, which is no good, and the algorithm rolls back days to the
# last day of the month, so we get the 28th.
(main)> feb_28_2012.delta(days:1, years:1)
=> 2013-02-28 17:00:00 -0700

# adding 2 days puts us into March, which then "looks right", but it's both
# right AND wrong, depending on how you look at it.  Another example is below,
# where we add a month to January 30th.  Really, though, think of this: how
# often do you need to add a year AND a day!?  Adding a year is more common, and
# this is showing that adding a year to Feb 29th will give you Feb 28th, which I
# think is better than March 1st.
(main)> feb_28_2012.delta(days:2, years:1)
=> 2013-03-01 17:00:00 -0700

# Crazier: add a day (fab 29th), then a month (march 29th), THEN a year.
(main)> feb_28_2012.delta(days:1, years:1, months:1)
=> 2013-03-29 17:00:00 -0600

# k, for the next examples, we need a new date, and this is a non-leap year.
(main)> jan_29_2013 = feb_28_2012.delta(days:1, months:11)
=> 2013-01-29 17:00:00 -0700

# what is 1/29/2013 plus two months?  easy!  march 29, 2013
(main)> jan_29_2013.delta(months:2)
=> 2013-03-29 17:00:00 -0600

# Yeah, smart guy?  Well then what is 1/29/2013 plus ONE month. It's feb 28th.
# When someone says "see you in a month!" they mean "next month", not "in the
# early part of two months in the future", which is where the math will take you
# if you don't add a "day of month" correction.
(main)> jan_29_2013.delta(months:1)
=> 2013-02-28 17:00:00 -0700
# but last year was a leap year, so we should get Feb 29th, 2012:
(main)> jan_29_2013.delta(months:1, years: -1)
=> 2012-02-29 17:00:00 -0700  # success!

# do other deltas work in reverse?  fuuuuuu...
(main)> jan_29_2013.delta(months:-11)
=> 2012-02-29 17:00:00 -0700
# ...ck yeah!  :-)

# unfortunately you will, in the edge cases, end up with stuff like this:
(main)> feb_28_2012 == feb_28_2012.delta(days:1, months:12).delta(months:-12)
=> true

Numeric conversions might look odd to you, and that's OK

Okay, no one EVER uses degrees, right? Always radians. So converting FROM radians is not as common, and when you do it you are doing some gnarly floating point division, so you won't get nice whole numbers

> 91.0 / 180.0 * Math::PI / Math::PI * 180.0
=> 90.9999694824219

So, anyway, there's my justification for this:

(main)> 90.degrees
=> 1.57079601287842
(main)> 91.degrees
=> 1.58824872970581

# bear with me...
(main)> Math::sin(90.degrees)
=> 1.0

# and that's not all!  How about those multiples of pi!?
(main)> Math::sin(0.5.pi)
=> 1.0
(main)> Math::sin(1.5.pi)
=> -1.0
# awww, yeah.

I'm sorry if that offends you, but I think it looks great! rotate 45.degrees just looks COOL to me! I hope you agree. Different strokes for different code monkies, I guess.

When would you not want to present a full screen modal? That's crazy. I had a bug where my modal was appearing BENEATH my custom tabs. No bueƱo! So I wrote these bad boys to get it done right everytime:

include SugarCube::Modal

present_modal(my_modal_controller)
# ...
dismiss_modal

The presentViewController method expects a target, which is the ViewController presenting your modal. I'm using UIApplication.sharedApplication.keyWindow.rootViewController for this target, which means that no matter WHERE you call it from, your modal view controller will be on top of all the other views. BAM.

UITextView is not a UIControl but what-do-i-care!?

The things I do with UIControls are pretty simple. It's change events. That's what I listen for, and they make it easy to do, right? But UITextView just HAAAD to be different, and instead emits notifications using the NSNotficationCenter. I've add the on :event methods to UITextView so you don't have to care, and can treat it like another (SugarCube-powered) UIControl.

EXCEPT! Notifications are observed, and you have to STOP observing them at some point, or something bad happens (think LOST and the weird timer from season 1).

text_control = UITextField.new
text_view = UITextView.new

text_control.on :change {
  p text_control.text
}

text_view.on :change {
  p text_view.text
}

text_control.off
text_view.off

More NSNotfication additions

Notifications are strings, pretty much. I mean, they start as strings, and then you emit them and listen for them. But at their heart, they are strings.

so.

# my_notification = i'm not telling, it's a secret.
my_notification.post_notification
my_notification.add_observer(self, :some_event_method)
my_notification.remove_observer(self)

> p my_notification
=> "my Notification"  # see?  it was a darn string the whole time!

It's important to unobserve. Again, think of that evil death timer. Scary stuff.

Where to now!?

I'm concerned about the cross-talk between BubbleWrap and SugarCube. I'm trying to keep the theme of SugarCube to be "getting work done", and in that vein I'm never going to build the wrappers (HTTP, EventMachine) that BubbleWrap has, and I'm not gonna compete with the App and Device singletons that BubbleWrap offers. They do what they do perfectly, and I already use them extensively.

That said, I have a different vision of the gesture recognizers, and I think they have a place in SugarCube. We're already offering competing versions of observation (actually, the two versions are complementary - BubbleWrap supports blocks, while SugarCube supports target/action), maybe it's good to have these different wrappers.

Please tell me what you think.