The Innovative College Drop Out Is Dead

Some tech blogs have recently talked about the failure of silicon valley (in general) to innovate at a similar pace to what was going on years ago with the pc then smartphone revolutions.

I can’t say I’m surprised that things have come to a halt. There’s a limited pool of world changing ideas that get executed well enough to actually change the world. There’s an even smaller pool of world changing ideas that don’t require intense Ph.D. research over a number of years and I believe we’re coming to the end of the era when a harvard freshman can sit down in his dorm room and create a website that massively impacts humanity. In a word, the low hanging fruit is gone.

The next few years of actual innovation will come from older CEOs that have the chops to steer companies that are taking on big challenges, not the next great ios mail client. I think software, in particular web/mobile app software will still enable people to turn a profit and will still create millionaires for years to come, but the world won’t change as a result. Things will still stay more or less the same. I guess as an entrepreneur, you have to decide what you want out of life, fame and fortune or just a steady business that enables you and your family to live life on your own time.

Customizing UITableViews With RubyMotion

You’re the master of UITableViews, NONE CAN STAND IN YOUR WAY TO GLOBAL DOMINATION. Whoa, intense. How are they looking though? Defaultish? The cure to those default ios looking uitableviews lies ahead. Be cautious though, we’re going to get mother flippin’ cray up in her’ I won’t bore you with how to write a uitableview subclass from scratch, instead I’m going to focus on some of the more popular tableview ui patterns of the day.

Gestures Are The New Hotness

You’ve used clear and you want to know the secret recipe to gestural tableviews in rubymotion.

Well you could try to read through this GestureTable app example code

OR

You could just read my simplified (albeit very procedural) and ready to understand version below.

This is what the goal looks like

Create your cells with a view in the front and a view in the back

table_view.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def tableView(tableView, cellForRowAtIndexPath: indexPath)
  # There's a bunch of other code before this
  ...

  # This is going to be the front view
  # Use this view as if it were the contentView
  view = UIView.new
  view.frame = CGRectMake(0, 0, 320, 75.5) # 75.5 is the cell row height

  ...

  # Here you add a hypothetical label
  view.addSubview(label)

  ...

  # And maybe a detail label
  view.addSubview(detailLabel)

  # Here is where you add the release to delete view
  imageView = UIImageView.new
  imageView.image = UIImage.imageNamed('images/delete.png')
  imageView.layer.opacity = 0.0
  imageView.backgroundColor = UIColor.blackColor

  # It all comes down to this
  # The first view you add to the contentView should be the view you want in the back
  # The second view you add should be the view you want visible before you activate the pan gesture
  cell.contentView.backgroundColor = UIColor.blackColor
  cell.contentView.addSubview(imageView)
  cell.contentView.addSubview(view)
end

Build that cool swipe to reveal/delete panning gesture

gesture_recognizer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class GestureRecognizer

  # Constants! You'll see haha
  SwipeToDeleteBreakpoint = 180
  RowAnimationDuration = 0.25

  def initWithTableView(tableView, delegate:delegate)
    if init
      @tableView = tableView
      @delegate = delegate
      @state = :none
      @tableViewDelegate = tableView.delegate
      tableView.delegate = self

      @panRecognizer = UIPanGestureRecognizer.alloc.initWithTarget(self, action: :"panGestureRecognizer:")
      tableView.gestureRecognizers += [@panRecognizer]
      @panRecognizer.delegate = self
    end
    self
  end

  # This is the whole reason you MUST OVERRIDE the default GestureRecognizer class
  # If you don't implement this method you'll just wind up with a broken table that you can't scroll vertically
  def gestureRecognizerShouldBegin(gestureRecognizer)

    indexPath = indexPathFromRecognizer(gestureRecognizer)
    if gestureRecognizer == @panRecognizer
      point = gestureRecognizer.translationInView(@tableView)
      # This is where the non-gesture blocking magic of this method happens
      # Ignore all other panning gestures except horizontal ones
      if point.y.abs > point.x.abs || indexPath.nil?
        false
      elsif indexPath
        @delegate.gestureRecognizer(self, canEditRowAtIndexPath: indexPath)
      end
    end
  end

  # This is the meaty meat of this class
  # This is where you take what would be a complicated problem and make it outrageously simple
  def panGestureRecognizer(recognizer)
    if (recognizer.state == UIGestureRecognizerStateBegan ||
        recognizer.state == UIGestureRecognizerStateChanged) &&
        recognizer.numberOfTouches != 0

      # This is the part that does the "animating"


      # First you get the indexPath of the cell and then the cell itself that you want to slide over to the right
      touchLocation = recognizer.locationOfTouch(0, inView: @tableView)
      @gesturingIndexPath = @tableView.indexPathForRowAtPoint(touchLocation)
      cell = @tableView.cellForRowAtIndexPath(@gesturingIndexPath)

      # Call this method to get the panning gesture's offset from the touch point
      translation = recognizer.translationInView(@tableView)

      # Make sure you can only slide the cell to the right and not the left
      # This code will get called every pixel you slide from left to right on a cell
      if translation.x > 0

        # This is the view behind the cell's regular content view
        cell.contentView.subviews[0].frame = CGRectOffset(CGRectMake(15, 22, 179.5, 31), 0, 0)
        cell.contentView.subviews[0].layer.opacity = translation.x / 179.5

        # Set the view's frame
        cell.contentView.subviews[1].frame = CGRectOffset(cell.contentView.bounds, translation.x, 0)
      end
    elsif recognizer.state == UIGestureRecognizerStateEnded

      # You've stopped sliding your finger along the cell at this point
      # Only one thing to do now is decide whether you've stopped sliding your finger at the "deletion" point

      # Grab the cell and the translation object again
      cell = @tableView.cellForRowAtIndexPath(@gesturingIndexPath)
      translation = recognizer.translationInView(@tableView)

      # Has your finger stopped at some point past or on the deletion point?
      if translation.x >= SwipeToDeleteBreakpoint

        # Yes it has!
        UIView.beginAnimations('', context: nil)
        cell.contentView.subviews[1].frame = CGRectOffset(cell.contentView.bounds, 0, 0)
        UIView.commitAnimations

        indexPath = @tableView.indexPathForCell(cell)

        EM.add_timer RowAnimationDuration do
          # This is where you update your data
          @tableView.beginUpdates
          @tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimationFade)
          @tableView.endUpdates
        end
      else
        # Nope, it hasn't just animate back to x == 0
        UIView.beginAnimations('', context: nil)
        cell.contentView.subviews[0].layer.opacity = 0.0
        cell.contentView.subviews[1].frame = cell.contentView.bounds
        UIView.commitAnimations
      end
    end
  end

  # Helper method to easily get the indexPath from the current touch point
  def indexPathFromRecognizer(recognizer)
    location = recognizer.locationInView(@tableView)
    @tableView.indexPathForRowAtPoint(location)
  end
end

And that’s that, a sample of how to get swipe to reveal working without doing blindly following stackoverflow.

An Intro to Core Data With Ruby Motion

So you want to store your rubymotion app’s data in a persistent data store but don’t want to have to connect to a remote server? Well fear not! This guide will teach you the basics of getting your data stored in the local ios sqlite database in no time!

And Then There Were Two

This requires just two files! Two! That’s nothing you scoff! I can make two files in my sleep you say! Yeah, we’ll see about that.

The first file is going to be our object that we want to store. You start out by subclassing NSManagedObject, overriding one method and writing a method to define accessors in another.

This file is the shorter of the two files and it goes a little something like this:

The Task Object - task.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Task < NSManagedObject

  # This is how you set database defaults, weird, I know
  def awakeFromInsert
    super

    setValue(Time.now, forKey:'created_at')
    setValue(Time.now, forKey:'updated_at')
    setValue('Edit Me!', forKey: 'title')
    setValue('Edit Me Too!', forKey: 'description')
  end

  # This is the part that will enable our object to interact with the sqlite database
  def self.entity
    @entity ||= begin
      entity = NSEntityDescription.new
      entity.name = 'Task'
      entity.managedObjectClassName = 'Task'
      entity.properties =
        ['title', NSStringAttributeType,
         'description', NSStringAttributeType,
         'created_at', NSDateAttributeType,
         'updated_at', NSDateAttributeType].each_slice(2).map do |name, type|
            property = NSAttributeDescription.new
            property.name = name
            property.attributeType = type
            property.optional = false
            property
          end
      entity
    end
  end
end

And Then There Was One

The second file is going to be the file that sets up the sqlite database and our object’s access to it:

The Task Store - task_store.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class TaskStore
  # Singleton me baby!
  def self.shared
    @shared ||= TaskStore.new
  end

  # The list of tasks
  def tasks
    @tasks ||= begin
      # Each sql query starts with an NSFetchRequest, duh! Yeah it wasn't obvious to me either
      request = NSFetchRequest.new
      # I think you can see how you can generalize this to any object when you write a gem (GO FORTH AND WRITE ONE)
      request.entity = NSEntityDescription.entityForName('Task', inManagedObjectContext:@context)
      request.sortDescriptors = [NSSortDescriptor.alloc.initWithKey('created_at', ascending:false)]

      # mmmm pointers
      error_ptr = Pointer.new(:object)
      data = @context.executeFetchRequest(request, error:error_ptr)
      raise "Error when fetching data: #{error_ptr[0].description}" if data == nil # probably should handle this differently
      data
    end
  end

  # CRUD time has begun!
  def addTask
    yield NSEntityDescription.insertNewObjectForEntityForName('Task', inManagedObjectContext:@context)
    save
  end

  def updateTask(id, attributes = {})
    error_ptr = Pointer.new(:object)
    task = @context.existingObjectWithID(id, error:error_ptr)
    attributes.each do |key, value|
      task.send("#{key}=", value) if task.respond_to?(key)
    end
    task.updated_at = Time.now
    save
  end

  def removeTask(task)
    @context.deleteObject(task)
    save
  end

  private

  def initialize
    model = NSManagedObjectModel.new
    model.entities = [Task.entity] # this is how you use the self.entity method from earlier

    store = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model)
    store_url = NSURL.fileURLWithPath(File.join(NSHomeDirectory(), 'Documents', 'Tasks.sqlite'))
    error_ptr = Pointer.new(:object)
    raise "Can't add persistent SQLite store: #{error_ptr[0].description}" unless store.addPersistentStoreWithType(NSSQLiteStoreType, configuration:nil, URL:store_url, options:nil, error:error_ptr)

    context = NSManagedObjectContext.new
    context.persistentStoreCoordinator = store
    @context = context
  end

  def save
    error_ptr = Pointer.new(:object)
    raise "Error when saving the model: #{error_ptr[0].description}" unless @context.save(error_ptr)
    @tasks = nil
  end
end

Where To Go From Here

And that’s it! Not too bad but it could much, much better, much DRYer and more activerecord like

For the not-so-curious, this is how you would use this new found knowledge of yours:

Use it or lose it! - tasks_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def viewDidLoad
  TaskStore.shared.addTask do |task|
    task.title = @theoreticalTextField.text
    task.description = ''
    task.created_at = Time.now
    task.updated_at = Time.now
  end

  task = TaskStore.shared.tasks.first

  TaskStore.shared.updateTask(task.objectID, {title: 'oh no you didnt!'})

  TaskStore.shared.removeTask(task)
end