├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── motion-settings-bundle.rb └── motion-settings-bundle │ ├── configuration.rb │ ├── generator.rb │ └── version.rb ├── motion-settings-bundle.gemspec ├── screenshot1.png ├── screenshot2.png └── screenshot3.png /.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 | MotionSettingsTest 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in motion-settings-bundle.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # motion-settings-bundle 2 | 3 | Create a Settings.bundle for your RubyMotion app. This allows your app to have a "global" settings entry in the Settings app. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | gem 'motion-settings-bundle' 10 | 11 | And then execute: 12 | 13 | bundle 14 | 15 | Or install it yourself as: 16 | 17 | gem install motion-settings-bundle 18 | 19 | ## Usage 20 | 21 | Add a chunk of code into your project's `Rakefile` like so: 22 | 23 | ``` ruby 24 | require 'motion-settings-bundle' 25 | 26 | Motion::SettingsBundle.setup do |app| 27 | # A text field. Allows configuration of a string. 28 | app.text "Name", key: "username", default: "Paul Atreides" 29 | app.text "E-mail", key: "email", keyboard: "EmailAddress", autocapitalization: "None" 30 | app.text "Password", key: "password", secure: true 31 | 32 | # A read-only text field. Use for showing a small chunk of text, maybe a version number 33 | app.title "Year of Birth", key: "yearOfBirth", default: "10,175 AG" 34 | 35 | # An on/off switch. Turn something on or off. Default is `false` (off). 36 | app.toggle "Kwisatz Haderach?", key: "superpowersEnabled", default: true 37 | 38 | # MultiValue, to choose from some item 39 | app.multivalue "Picture Resolution 1", key: "pictureResolution1", default: "1500", values: ["1500", "1200", "1000"] 40 | 41 | # MultiValue 2, with values and different titles (required when the values are numbers not string) 42 | app.multivalue "Picture Resolution 2", key: "pictureResolution2", default: 1500, values: [1500, 1200, 1000], 43 | titles: ["1500px", "1200px", "1000px"] 44 | 45 | # A slider, configure volume or something linear 46 | app.slider "Spice Level", key: "spiceLevel", default: 50, min: 1, max: 100 47 | 48 | # Child pane to display licenses in 49 | app.child "Acknowledgements" do |ack| 50 | ack.child "AwesomeOSSLibrary" do |lic| 51 | lic.group "Copyright 2013 AwesomeOSSContributor" 52 | lic.group "More license text that is terribly formatted but fulfills legal requirements" 53 | end 54 | end 55 | end 56 | ``` 57 | 58 | Now just run `rake` as normal! 59 | 60 | This should now add a `Settings.bundle` folder into your `resources` directory. Make sure to commit it! If you ever change the data in the `Settings.setup` block, it will be re-built on the next `rake` run. You'll end up with something like this: 61 | 62 | ![screenshot1 of motion-settings-bundle](https://raw.github.com/qrush/motion-settings-bundle/master/screenshot1.png) 63 | ![screenshot2 of motion-settings-bundle](https://raw.github.com/qrush/motion-settings-bundle/master/screenshot2.png) 64 | ![screenshot3 of motion-settings-bundle](https://raw.github.com/qrush/motion-settings-bundle/master/screenshot3.png) 65 | 66 | If you're wondering how to access this in code, it's pretty easy: 67 | 68 | ``` ruby 69 | NSUserDefaults.standardUserDefaults["username"] 70 | # returns "Paul Atreides" 71 | ``` 72 | 73 | And so on. Just remember, the defaults aren't populated until your user actually opens the Settings app, so make sure to handle all of your setting entries being `nil`. 74 | 75 | ## TODO 76 | 77 | This project really solely exists to avoid creating/editing the Settings.bundle in XCode. The specs for it so far have been based off [mordaroso/rubymotion-settings-bundle](https://github/mordaroso/rubymotion-settings-bundle). Please feel free to contribute more ways to generate settings! 78 | 79 | * Add a custom label, "Settings for Blah" 80 | * Don't re-create files every time 81 | * Slider default level doesn't seem to work yet 82 | 83 | ## Contributing 84 | 85 | 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, and then open the Settings app. 86 | 87 | If you've added a setting it would be really nice if you could update `screenshot.png` as well! 88 | 89 | 1. Fork it 90 | 2. Create your feature branch (`git checkout -b my-new-feature`) 91 | 3. Commit your changes (`git commit -am 'Add some feature'`) 92 | 4. Push to the branch (`git push origin my-new-feature`) 93 | 5. Create new Pull Request 94 | 95 | ## License 96 | 97 | MIT. See `LICENSE`. 98 | -------------------------------------------------------------------------------- /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 = "MotionSettingsTest" 7 | `rm -rf #{dir}` 8 | `motion create #{dir}` 9 | Dir.chdir(dir) do 10 | File.open("Gemfile", "w") do |f| 11 | f.write <<-EOF 12 | source :rubygems 13 | gem "motion-settings-bundle", :path => ".." 14 | EOF 15 | end 16 | 17 | `bundle --local` 18 | 19 | File.open("Rakefile", "a") do |f| 20 | f.write <<-EOF 21 | 22 | require 'bundler' 23 | Bundler.setup 24 | 25 | require 'motion-settings-bundle' 26 | 27 | Motion::SettingsBundle.setup do |app| 28 | # A text field. Allows configuration of a string. 29 | app.text "Name", key: "username", default: "Paul Atreides" 30 | app.text "E-mail", key: "email", keyboard: "EmailAddress", autocapitalization: "None" 31 | app.text "Password", key: "password", secure: true 32 | 33 | # A read-only text field. Use for showing a small chunk of text, maybe a version number 34 | app.title "Year of Birth", key: "yearOfBirth", default: "10,175 AG" 35 | 36 | # An on/off switch. Turn something on or off. Default is `false` (off). 37 | app.toggle "Kwisatz Haderach?", key: "superpowersEnabled", default: true 38 | 39 | # A slider, configure volume or something linear 40 | app.slider "Spice Level", key: "spiceLevel", default: 50, min: 1, max: 100 41 | 42 | # Child pane to display licenses in 43 | app.child "Acknowledgements" do |ack| 44 | ack.child "AwesomeOSSLibrary" do |lic| 45 | lic.group "Copyright 2013 AwesomeOSSContributor" 46 | lic.group "More license text that is terribly formatted but fulfills legal requirements" 47 | end 48 | end 49 | end 50 | EOF 51 | end 52 | 53 | puts "*" * 80 54 | puts "Booting up the simulator! Jump to the Settings app once booted and open '#{dir}'." 55 | puts "*" * 80 56 | sh "rake" 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/motion-settings-bundle.rb: -------------------------------------------------------------------------------- 1 | unless defined?(Motion::Project::Config) 2 | raise "This file must be required within a RubyMotion project Rakefile." 3 | end 4 | 5 | require "fileutils" 6 | 7 | require "plist" 8 | 9 | require "motion-settings-bundle/configuration" 10 | require "motion-settings-bundle/generator" 11 | require "motion-settings-bundle/version" 12 | 13 | module Motion 14 | module SettingsBundle 15 | class << self 16 | attr_accessor :generator 17 | 18 | def setup(&block) 19 | generator.configure(Configuration.new(&block)) 20 | end 21 | end 22 | 23 | self.generator = Generator.new(App.config.resources_dirs.first) 24 | end 25 | end 26 | 27 | desc "Generate a Settings.bundle" 28 | task :settings do 29 | Motion::SettingsBundle.generator.generate 30 | end 31 | 32 | %w(build:simulator build:device).each do |build_task| 33 | Rake::Task[build_task].enhance([:settings]) 34 | end 35 | -------------------------------------------------------------------------------- /lib/motion-settings-bundle/configuration.rb: -------------------------------------------------------------------------------- 1 | module Motion 2 | module SettingsBundle 3 | class Configuration 4 | attr_reader :preferences, :children 5 | 6 | def initialize(&block) 7 | @preferences = [] 8 | @children = {} 9 | block.call(self) 10 | end 11 | 12 | def text(title, options = {}) 13 | preference(title, "PSTextFieldSpecifier", options, { 14 | "IsSecure" => options[:secure] || false, 15 | "KeyboardType" => options[:keyboard] || "Alphabet", 16 | "AutocapitalizationType" => options[:autocapitalization] || "Sentences", 17 | "AutocorrectionType" => options[:autocorrection] || "Default" 18 | }) 19 | end 20 | 21 | def title(title, options = {}) 22 | preference(title, "PSTitleValueSpecifier", options) 23 | end 24 | 25 | def multivalue(title, options = {}) 26 | preference(title, "PSMultiValueSpecifier", options, { 27 | "Values" => options[:values], 28 | "Titles" => options[:titles].nil? ? options[:values] : options[:titles] 29 | }) 30 | end 31 | 32 | def toggle(title, options = {}) 33 | preference(title, "PSToggleSwitchSpecifier", options, { 34 | "TrueValue" => true, 35 | "FalseValue" => false 36 | }) 37 | end 38 | 39 | def slider(title, options = {}) 40 | preference(title, "PSSliderSpecifier", options, { 41 | "MinimumValue" => options[:min], 42 | "MaximumValue" => options[:max] 43 | }) 44 | end 45 | 46 | def group(title, options = {}) 47 | group = {"Title" => title, "Type" => "PSGroupSpecifier"} 48 | group["FooterText"] = options[:footer] if options[:footer] 49 | @preferences << group 50 | end 51 | 52 | def child(title, options = {}, &block) 53 | @preferences << {"Title" => title, "File" => title, "Type" => "PSChildPaneSpecifier"}.merge(options) 54 | @children[title] = Configuration.new(&block) 55 | end 56 | 57 | private 58 | 59 | def preference(title, type, options, extras = {}) 60 | @preferences << {"Title" => title, "Key" => options[:key], "DefaultValue" => options[:default], "Type" => type}.merge(extras) 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/motion-settings-bundle/generator.rb: -------------------------------------------------------------------------------- 1 | module Motion 2 | module SettingsBundle 3 | class Generator 4 | def initialize(resources_dir) 5 | @root_path = File.join(resources_dir, "Settings.bundle") 6 | end 7 | 8 | def configure(configuration) 9 | @configuration = configuration 10 | end 11 | 12 | def generate 13 | directory @root_path 14 | 15 | strings_path = File.join(@root_path, "en.lproj") 16 | directory strings_path 17 | 18 | strings_file_path = File.join(strings_path, "Root.strings") 19 | file strings_file_path do |io| 20 | io.write <<-EOF 21 | /* A single strings file, whose title is specified in your preferences schema. The strings files provide the localized content to display to the user for each of your preferences. */ 22 | 23 | "Group" = "Group"; 24 | "Name" = "Name"; 25 | "none given" = "none given"; 26 | "Enabled" = "Enabled"; 27 | EOF 28 | end 29 | 30 | plist_file_path = File.join(@root_path, "Root.plist") 31 | 32 | file(plist_file_path, true) do |io| 33 | io.write({ 34 | "Title" => "Settings", 35 | "StringsTable" => "Root", 36 | "PreferenceSpecifiers" => @configuration.preferences 37 | }.to_plist) 38 | end 39 | 40 | generate_children(@configuration.children) 41 | end 42 | 43 | private 44 | 45 | def generate_children(children) 46 | children.each do |title, child| 47 | file(File.join(@root_path, "#{title}.plist"), true) do |io| 48 | io.write({ 49 | "Title" => title, 50 | "StringsTable" => title, 51 | "PreferenceSpecifiers" => child.preferences 52 | }.to_plist) 53 | end 54 | 55 | generate_children(child.children) 56 | end 57 | end 58 | 59 | def directory(path) 60 | unless File.exist?(path) 61 | FileUtils.mkdir(path) 62 | App.info "Create", path 63 | end 64 | end 65 | 66 | def file(path, force = false) 67 | if force || !File.exist?(path) 68 | File.open(path, "w") do |io| 69 | yield io 70 | end 71 | App.info "Create", path 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/motion-settings-bundle/version.rb: -------------------------------------------------------------------------------- 1 | module Motion 2 | module SettingsBundle 3 | VERSION = "0.3.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /motion-settings-bundle.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/motion-settings-bundle/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Nick Quaranto"] 6 | gem.email = ["nick@quaran.to"] 7 | gem.summary = gem.description = 8 | %{Create a Settings.bundle for your RubyMotion app. This allows your app to have a "global" settings entry in the Settings app.} 9 | gem.homepage = "https://github.com/qrush/motion-settings-bundle" 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "motion-settings-bundle" 15 | gem.require_paths = ["lib"] 16 | gem.version = Motion::SettingsBundle::VERSION 17 | 18 | gem.add_dependency 'plist', '~> 3.1.0' 19 | gem.add_development_dependency 'rake' 20 | end 21 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qrush/motion-settings-bundle/c99c2a32f5196dfc8de1a4f6b06a5f2b48abd74a/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qrush/motion-settings-bundle/c99c2a32f5196dfc8de1a4f6b06a5f2b48abd74a/screenshot2.png -------------------------------------------------------------------------------- /screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qrush/motion-settings-bundle/c99c2a32f5196dfc8de1a4f6b06a5f2b48abd74a/screenshot3.png --------------------------------------------------------------------------------