Some techblogs
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 mailclient. 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.
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.
deftableView(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 contentViewview=UIView.newview.frame=CGRectMake(0,0,320,75.5)# 75.5 is the cell row height...# Here you add a hypothetical labelview.addSubview(label)...# And maybe a detail labelview.addSubview(detailLabel)# Here is where you add the release to delete viewimageView=UIImageView.newimageView.image=UIImage.imageNamed('images/delete.png')imageView.layer.opacity=0.0imageView.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 gesturecell.contentView.backgroundColor=UIColor.blackColorcell.contentView.addSubview(imageView)cell.contentView.addSubview(view)end
Build that cool swipe to reveal/delete panning gesture
classGestureRecognizer# Constants! You'll see hahaSwipeToDeleteBreakpoint=180RowAnimationDuration=0.25definitWithTableView(tableView,delegate:delegate)ifinit@tableView=tableView@delegate=delegate@state=:none@tableViewDelegate=tableView.delegatetableView.delegate=self@panRecognizer=UIPanGestureRecognizer.alloc.initWithTarget(self,action::"panGestureRecognizer:")tableView.gestureRecognizers+=[@panRecognizer]@panRecognizer.delegate=selfendselfend# 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 verticallydefgestureRecognizerShouldBegin(gestureRecognizer)indexPath=indexPathFromRecognizer(gestureRecognizer)ifgestureRecognizer==@panRecognizerpoint=gestureRecognizer.translationInView(@tableView)# This is where the non-gesture blocking magic of this method happens# Ignore all other panning gestures except horizontal onesifpoint.y.abs>point.x.abs||indexPath.nil?falseelsifindexPath@delegate.gestureRecognizer(self,canEditRowAtIndexPath:indexPath)endendend# This is the meaty meat of this class# This is where you take what would be a complicated problem and make it outrageously simpledefpanGestureRecognizer(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 righttouchLocation=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 pointtranslation=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 celliftranslation.x>0# This is the view behind the cell's regular content viewcell.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 framecell.contentView.subviews[1].frame=CGRectOffset(cell.contentView.bounds,translation.x,0)endelsifrecognizer.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 againcell=@tableView.cellForRowAtIndexPath(@gesturingIndexPath)translation=recognizer.translationInView(@tableView)# Has your finger stopped at some point past or on the deletion point?iftranslation.x>=SwipeToDeleteBreakpoint# Yes it has!UIView.beginAnimations('',context:nil)cell.contentView.subviews[1].frame=CGRectOffset(cell.contentView.bounds,0,0)UIView.commitAnimationsindexPath=@tableView.indexPathForCell(cell)EM.add_timerRowAnimationDurationdo# This is where you update your data@tableView.beginUpdates@tableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:UITableViewRowAnimationFade)@tableView.endUpdatesendelse# Nope, it hasn't just animate back to x == 0UIView.beginAnimations('',context:nil)cell.contentView.subviews[0].layer.opacity=0.0cell.contentView.subviews[1].frame=cell.contentView.boundsUIView.commitAnimationsendendend# Helper method to easily get the indexPath from the current touch pointdefindexPathFromRecognizer(recognizer)location=recognizer.locationInView(@tableView)@tableView.indexPathForRowAtPoint(location)endend
And that’s that, a sample of how to get swipe to reveal working without doing blindly following stackoverflow.
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:
classTask<NSManagedObject# This is how you set database defaults, weird, I knowdefawakeFromInsertsupersetValue(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 databasedefself.entity@entity||=beginentity=NSEntityDescription.newentity.name='Task'entity.managedObjectClassName='Task'entity.properties=['title',NSStringAttributeType,'description',NSStringAttributeType,'created_at',NSDateAttributeType,'updated_at',NSDateAttributeType].each_slice(2).mapdo|name,type|property=NSAttributeDescription.newproperty.name=nameproperty.attributeType=typeproperty.optional=falsepropertyendentityendendend
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:
classTaskStore# Singleton me baby!defself.shared@shared||=TaskStore.newend# The list of tasksdeftasks@tasks||=begin# Each sql query starts with an NSFetchRequest, duh! Yeah it wasn't obvious to me eitherrequest=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 pointerserror_ptr=Pointer.new(:object)data=@context.executeFetchRequest(request,error:error_ptr)raise"Error when fetching data: #{error_ptr[0].description}"ifdata==nil# probably should handle this differentlydataendend# CRUD time has begun!defaddTaskyieldNSEntityDescription.insertNewObjectForEntityForName('Task',inManagedObjectContext:@context)saveenddefupdateTask(id,attributes={})error_ptr=Pointer.new(:object)task=@context.existingObjectWithID(id,error:error_ptr)attributes.eachdo|key,value|task.send("#{key}=",value)iftask.respond_to?(key)endtask.updated_at=Time.nowsaveenddefremoveTask(task)@context.deleteObject(task)saveendprivatedefinitializemodel=NSManagedObjectModel.newmodel.entities=[Task.entity]# this is how you use the self.entity method from earlierstore=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}"unlessstore.addPersistentStoreWithType(NSSQLiteStoreType,configuration:nil,URL:store_url,options:nil,error:error_ptr)context=NSManagedObjectContext.newcontext.persistentStoreCoordinator=store@context=contextenddefsaveerror_ptr=Pointer.new(:object)raise"Error when saving the model: #{error_ptr[0].description}"unless@context.save(error_ptr)@tasks=nilendend
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
1234567891011121314
defviewDidLoadTaskStore.shared.addTaskdo|task|task.title=@theoreticalTextField.texttask.description=''task.created_at=Time.nowtask.updated_at=Time.nowendtask=TaskStore.shared.tasks.firstTaskStore.shared.updateTask(task.objectID,{title:'oh no you didnt!'})TaskStore.shared.removeTask(task)end