├── example ├── Gemfile ├── Rakefile └── app │ ├── app_delegate.rb │ └── timer_controller.rb ├── screenshot1.png ├── screenshot2.png ├── lib ├── motion-layout │ ├── version.rb │ └── layout.rb └── motion-layout.rb ├── Gemfile ├── .gitignore ├── Rakefile ├── motion-layout.gemspec ├── LICENSE └── README.md /example/Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gem "motion-layout", :path => ".." 3 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qrush/motion-layout/HEAD/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qrush/motion-layout/HEAD/screenshot2.png -------------------------------------------------------------------------------- /lib/motion-layout/version.rb: -------------------------------------------------------------------------------- 1 | module Motion 2 | class Layout 3 | VERSION = "0.0.2" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in motion-layout.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | MotionLayoutTest/ 19 | -------------------------------------------------------------------------------- /example/Rakefile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | $:.unshift("/Library/RubyMotion/lib") 3 | require 'motion/project' 4 | 5 | require 'bundler' 6 | Bundler.setup 7 | 8 | require 'motion-layout' 9 | 10 | Motion::Project::App.setup do |app| 11 | app.name = 'MotionLayoutTest' 12 | end 13 | -------------------------------------------------------------------------------- /lib/motion-layout.rb: -------------------------------------------------------------------------------- 1 | unless defined?(Motion::Project::Config) 2 | raise "This file must be required within a RubyMotion project Rakefile." 3 | end 4 | 5 | Motion::Project::App.setup do |app| 6 | Dir.glob(File.join(File.dirname(__FILE__), 'motion-layout/*.rb')).each do |file| 7 | app.files.unshift(file) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | 4 | desc "Test by generating an app" 5 | task :default do 6 | dir = "MotionLayoutTest" 7 | `rm -rf #{dir}` 8 | `motion create #{dir}` 9 | `rm -f #{dir}/app_delegate.rb` 10 | `cp -r example/* #{dir}/` 11 | 12 | Dir.chdir(dir) do 13 | sh "bundle --local && rake" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /motion-layout.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'motion-layout/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "motion-layout" 8 | gem.version = Motion::Layout::VERSION 9 | gem.authors = ["Nick Quaranto"] 10 | gem.email = ["nick@quaran.to"] 11 | gem.summary = gem.description = %q{A nice way to use iOS6+ autolayout in your RubyMotion app. Use ASCII-art inspired format strings to build your app's layout!} 12 | gem.homepage = "https://github.com/qrush/motion-layout" 13 | 14 | gem.files = `git ls-files`.split($/) 15 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 16 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 17 | gem.require_paths = ["lib"] 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Nick Quaranto 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/motion-layout/layout.rb: -------------------------------------------------------------------------------- 1 | module Motion 2 | class Layout 3 | def initialize(&block) 4 | @verticals = [] 5 | @horizontals = [] 6 | @metrics = {} 7 | 8 | yield self 9 | strain 10 | end 11 | 12 | def metrics(metrics) 13 | @metrics = Hash[metrics.keys.map(&:to_s).zip(metrics.values)] 14 | end 15 | 16 | def subviews(subviews) 17 | @subviews = Hash[subviews.keys.map(&:to_s).zip(subviews.values)] 18 | end 19 | 20 | def view(view) 21 | @view = view 22 | end 23 | 24 | def horizontal(horizontal) 25 | @horizontals << horizontal 26 | end 27 | 28 | def vertical(vertical) 29 | @verticals << vertical 30 | end 31 | 32 | private 33 | 34 | def strain 35 | @subviews.values.each do |subview| 36 | subview.translatesAutoresizingMaskIntoConstraints = false 37 | @view.addSubview(subview) unless subview.superview 38 | end 39 | 40 | views = @subviews.merge("superview" => @view) 41 | 42 | constraints = [] 43 | constraints += @verticals.map do |vertical| 44 | NSLayoutConstraint.constraintsWithVisualFormat("V:#{vertical}", options:NSLayoutFormatAlignAllCenterX, metrics:@metrics, views:views) 45 | end 46 | constraints += @horizontals.map do |horizontal| 47 | NSLayoutConstraint.constraintsWithVisualFormat("H:#{horizontal}", options:NSLayoutFormatAlignAllCenterY, metrics:@metrics, views:views) 48 | end 49 | 50 | @view.addConstraints(constraints.flatten) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /example/app/app_delegate.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 HipByte SPRL and Contributors 2 | # 3 | # Permission is hereby granted, free of charge, to any person 4 | # obtaining a copy of this software and associated documentation 5 | # files (the “Software”), to deal in the Software without 6 | # restriction, including without limitation the rights to use, 7 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the 9 | # Software is furnished to do so, subject to the following 10 | # conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be 13 | # included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | # OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | class AppDelegate 25 | def application(application, didFinishLaunchingWithOptions:launchOptions) 26 | @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 27 | @window.rootViewController = TimerController.alloc.init 28 | @window.rootViewController.wantsFullScreenLayout = true 29 | @window.makeKeyAndVisible 30 | true 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /example/app/timer_controller.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 HipByte SPRL and Contributors 2 | # 3 | # Permission is hereby granted, free of charge, to any person 4 | # obtaining a copy of this software and associated documentation 5 | # files (the “Software”), to deal in the Software without 6 | # restriction, including without limitation the rights to use, 7 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the 9 | # Software is furnished to do so, subject to the following 10 | # conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be 13 | # included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | # OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | class TimerController < UIViewController 25 | attr_reader :timer 26 | 27 | def viewDidLoad 28 | @state = UILabel.new 29 | @state.font = UIFont.systemFontOfSize(30) 30 | @state.text = 'Tap to start' 31 | @state.textAlignment = UITextAlignmentCenter 32 | @state.textColor = UIColor.whiteColor 33 | @state.backgroundColor = UIColor.clearColor 34 | 35 | @action = UIButton.buttonWithType(UIButtonTypeRoundedRect) 36 | @action.setTitle('Start', forState:UIControlStateNormal) 37 | @action.setTitle('Stop', forState:UIControlStateSelected) 38 | @action.addTarget(self, action:'actionTapped', forControlEvents:UIControlEventTouchUpInside) 39 | 40 | Motion::Layout.new do |layout| 41 | layout.view view 42 | layout.subviews "state" => @state, "action" => @action 43 | layout.metrics "top" => 200, "margin" => 20, "height" => 40 44 | layout.vertical "|-top-[state(==height)]-margin-[action(==height)]" 45 | layout.horizontal "|-margin-[state]-margin-|" 46 | layout.horizontal "|-margin-[action]-margin-|" 47 | end 48 | end 49 | 50 | def actionTapped 51 | if @timer 52 | @timer.invalidate 53 | @timer = nil 54 | else 55 | @duration = 0 56 | @timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target:self, selector:'timerFired', userInfo:nil, repeats:true) 57 | end 58 | @action.selected = !@action.selected? 59 | end 60 | 61 | def timerFired 62 | @state.text = "%.1f" % (@duration += 0.1) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # motion-layout 2 | 3 | A nice way to use iOS6+ autolayout in your RubyMotion app. Use ASCII-art inspired format strings to build your app's layout! 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | gem 'motion-layout' 10 | 11 | And then execute: 12 | 13 | bundle 14 | 15 | Or install it yourself as: 16 | 17 | gem install motion-layout 18 | 19 | Then in your `Rakefile`: 20 | 21 | require 'motion-layout' 22 | 23 | ## Usage 24 | 25 | Using AutoLayout is a way to put UI elements in your iPhone app without using Interface Builder, and without being very specific about pixel sizes, locations, etc. The layout strings are ASCII inspired, and Apple's documentation on the [Visual Format Language](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html) is a necessary read and reference. 26 | 27 | Here's an example of `Motion::Layout` usage from inside of [Basecamp for iPhone](https://itunes.apple.com/us/app/id599139477) on a `UITableView`'s `tableFooterView`: 28 | 29 | ![](https://raw.github.com/qrush/motion-layout/master/screenshot1.png) 30 | 31 | ``` ruby 32 | Motion::Layout.new do |layout| 33 | layout.view self.view.tableFooterView 34 | layout.subviews "switch" => @switch, "help" => @help 35 | layout.vertical "|-15-[switch]-10-[help(==switch)]-15-|" 36 | layout.horizontal "|-10-[switch]-10-|" 37 | layout.horizontal "|-10-[help]-10-|" 38 | end 39 | ``` 40 | 41 | And here's an example you can run right inside this repo, the Time app converted to use Auto Layout from the [RubyMotionSamples](https://github.com/HipByte/RubyMotionSamples) repo: 42 | 43 | ![](https://raw.github.com/qrush/motion-layout/master/screenshot2.png) 44 | 45 | ``` ruby 46 | Motion::Layout.new do |layout| 47 | layout.view view 48 | layout.subviews state: @state, action: @action 49 | layout.metrics "top" => 200, "margin" => 20, "height" => 40 50 | layout.vertical "|-top-[state(==height)]-margin-[action(==height)]" 51 | layout.horizontal "|-margin-[state]-margin-|" 52 | layout.horizontal "|-margin-[action]-margin-|" 53 | end 54 | ``` 55 | 56 | ## TODO 57 | 58 | * Support finer grained constraints 59 | * Better debugging messages 60 | * More examples 61 | 62 | ## Contributing 63 | 64 | I couldn't figure out how to test this automatically. Run `bundle` to get the gems you need, and then `rake` to generate a RubyMotion app in the iOS simulator 65 | 66 | 1. Fork it 67 | 2. Create your feature branch (`git checkout -b my-new-feature`) 68 | 3. Commit your changes (`git commit -am 'Add some feature'`) 69 | 4. Push to the branch (`git push origin my-new-feature`) 70 | 5. Create new Pull Request 71 | 72 | ## License 73 | 74 | MIT. See `LICENSE`. 75 | --------------------------------------------------------------------------------