├── README.md ├── install.md ├── ios-guide-01.md ├── ios-guide-02.md ├── ios-guide-03.md ├── ios-guide-04.md ├── more-repo.md ├── rails-guide-01.md ├── rails-guide-02.md └── resource.md /README.md: -------------------------------------------------------------------------------- 1 | # RedPotion 教程 2 | 3 | ## 開發需求 4 | 5 | * RubyMotion 最新版 6 | * Rails 最新版 7 | 8 | ## 安裝教程 9 | 10 | * [RedPotion 安裝指南](install.md) 11 | 12 | ## 開發步驟: 13 | 14 | * [rails-1](rails-guide-01.md) 15 | * [rails-2](rails-guide-02.md) 16 | * [ios-01](ios-guide-01.md) 17 | * [ios-02](ios-guide-02.md) 18 | * [ios-03](ios-guide-01.md) 19 | * [ios-04](ios-guide-01.md) 20 | 21 | ## ( JOB List )示範代碼地址: 22 | 23 | * iOS : https://github.com/quanzhanying/ios-job-list 24 | * API : https://github.com/quanzhanying/api-job-list 25 | 26 | ## (ArtStore )示範代碼地址: 27 | 28 | * iOS : https://github.com/growthschool/ios-artstore 29 | * API : https://github.com/growthschool/api-artstore 30 | 31 | ## RedPotion 資源 32 | 33 | * [好用工具以及文檔](resource.md) 34 | * [如何找到更多開源示範教材](more-repo.md) 35 | -------------------------------------------------------------------------------- /install.md: -------------------------------------------------------------------------------- 1 | # 如何安裝 RedPotion 2 | 3 | - 安裝 Xcode 9.3 4 | - 再去 rubymotion 官網下載 http://www.rubymotion.com/ 5 | - gem install cocoapods 6 | - pod setup (要非常久,因為 782mb ) 7 | - gem install redpotion 8 | - potion new meme 9 | - cd meme; rake pod:install 10 | - rake 11 | 12 | ## 如何 確定自己下載 cocoapods 的進度 13 | 14 | ``` 15 | while true; do 16 | du -sh ~/.cocoapods/ 17 | sleep 3 18 | done 19 | ``` 20 | 在 terminal 輸入這四行,可以確定自己下載 cocoapods 的進度 21 | 22 | http://stackoverflow.com/questions/21022638/pod-install-is-staying-on-setting-up-cocoapods-master-repo 23 | 24 | ## 特別注意: 25 | 26 | * 記得不要過 VPN 抓 rubymotion,否則會超慢 27 | -------------------------------------------------------------------------------- /ios-guide-01.md: -------------------------------------------------------------------------------- 1 | # 目標 2 | 3 | * 生成第一個 redpotion 專案 4 | * 拉 API 繪成第一個主畫面 5 | 6 | # 步驟: 7 | 8 | ## Step 1: 新增第一個 Project 9 | 10 | * potion new ios-job-list 11 | * rake 12 | 13 | 會顯示第一個畫面 Hello World 畫面。 14 | 15 | ![](https://cdn.filepicker.io/api/file/T3Ltl0RRW4aP8xO9jmTA) 16 | 17 | ## Step 2 : 修改標題 18 | 19 | * 打開 `app/screens/home_screen.rb` 修改 title 為 '職缺列表' 20 | * rake 21 | 22 | ![](https://cdn.filepicker.io/api/file/0K9SCgprSyOcGvXK2ysY) 23 | 24 | ## Step 3 : 準備 jobs 資料 25 | 26 | * 在負責吐資料的另一個網址,我們準備了職缺的資料。資料位址是 27 | 28 | 29 | 30 | ![](https://cdn.filepicker.io/api/file/nDjhLrM2Q8eOft6rBNLW) 31 | ## Step 4 : 接 api 32 | 33 | 修改 `app/screens/home_screen.rb` 變成 34 | 35 | ``` ruby 36 | class HomeScreen < PM::TableScreen 37 | title '職缺一覽' 38 | stylesheet HomeScreenStylesheet 39 | 40 | def on_load 41 | @jobs = [] 42 | load_jobs 43 | end 44 | 45 | def load_jobs 46 | Job.all do |response, jobs| 47 | if response.success? 48 | @jobs = jobs 49 | stop_refreshing 50 | update_table_data 51 | else 52 | app.alert 'Sorry, there was an error fetching the jobs.' 53 | mp response.error.localizedDescription 54 | end 55 | end 56 | end 57 | 58 | def table_data 59 | [{ 60 | cells: @jobs.map do |job| 61 | { 62 | height: 100, 63 | title: job.title, 64 | action: :view_job, 65 | arguments: { job: job } 66 | } 67 | end 68 | }] 69 | end 70 | 71 | def will_animate_rotate(_orientation, _duration) 72 | find.all.reapply_styles 73 | end 74 | end 75 | 76 | ``` 77 | 78 | 新增 `app/models/api_client.rb` 79 | 80 | ``` ruby 81 | 82 | class ApiClient 83 | class << self 84 | def client 85 | @client ||= AFMotion::SessionClient.build('http://localhost:3000/') do 86 | header 'Accept', 'application/json' 87 | response_serializer :json 88 | end 89 | end 90 | 91 | def update_authorization_header(header) 92 | client.headers['Authorization'] = header 93 | end 94 | end 95 | end 96 | 97 | ``` 98 | 99 | 新增 `app/models/job.rb` 100 | 101 | ``` ruby 102 | 103 | class Job 104 | attr_accessor :id, :title 105 | 106 | def initialize(data) 107 | @id = data['id'] 108 | @title = data['title'] 109 | end 110 | 111 | def self.all(&callback) 112 | ApiClient.client.get 'jobs' do |response| 113 | models = [] 114 | models = response.object.map { |data| new(data) } if response.success? 115 | callback.call(response, models) 116 | end 117 | end 118 | end 119 | 120 | ``` 121 | 122 | 記得要去 `Rakefile` 裡面加入這一行 123 | 124 | ``` 125 | app.info_plist['NSAppTransportSecurity'] = { 'NSAllowsArbitraryLoads' => true } # allow any HTTP request 126 | ``` 127 | 128 | 這樣 app 的網路才會通。 129 | 130 | 然後執行 131 | 132 | * rake 133 | 134 | 這樣你就收到剛剛的資料了。 135 | 136 | ![](https://cdn.filepicker.io/api/file/5WzgJhWLSDKJVlNn7CmP) 137 | 138 | 139 | ## 新增職缺敘述 140 | 141 | 我們想在列表上新增職缺敘述 142 | 143 | 144 | ![](https://cdn.filepicker.io/api/file/qqQTD3qiR2WgV9cuQLB1) 145 | 146 | 修改 `app/models/job.rb` 147 | 148 | ``` ruby 149 | 150 | class Job 151 | attr_accessor :id, :title, :content 152 | 153 | def initialize(data) 154 | @id = data['id'] 155 | @title = data['title'] 156 | @content = data['content'] 157 | end 158 | 159 | ``` 160 | 161 | 修改 `app/screens/home_screen.rb` 中的 table_data,加入 subtitle 162 | 163 | ``` ruby 164 | def table_data 165 | [{ 166 | cells: @jobs.map do |job| 167 | { 168 | height: 100, 169 | title: job.title, 170 | subtitle: job.content, 171 | action: :view_job, 172 | arguments: { job: job } 173 | } 174 | end 175 | }] 176 | end 177 | ``` 178 | 179 | # 資源: 180 | 181 | * Table Screen https://promotion.readthedocs.io/en/master/Reference/ProMotion%20TableScreen/ 182 | -------------------------------------------------------------------------------- /ios-guide-02.md: -------------------------------------------------------------------------------- 1 | # 目標: 2 | 3 | * 點擊工作,可以看到工作內容 4 | 5 | # 步驟: 6 | 7 | ## Step 1 8 | 9 | 在 `app/screens/homr_screen.rb` 加入 10 | 11 | ``` ruby 12 | def view_job(args) 13 | open JobScreen.new(args) 14 | end 15 | 16 | ``` 17 | 18 | ## Step 2 19 | 20 | * potion g screen job 21 | 22 | 23 | ## Step 3 24 | 25 | 修改 `app/screens/job_screen.rb` 26 | 27 | 加入 28 | 29 | ``` ruby 30 | 31 | class JobScreen < PM::Screen 32 | stylesheet JobScreenStylesheet 33 | 34 | attr_accessor :job 35 | 36 | def on_load 37 | self.title = @job.title 38 | 39 | @content = append!(UILabel, :job_content) 40 | @content.text = @job.content 41 | 42 | @image = append!(UIImageView, :job_image).style { |st| st.remote_image = @job.image_url } 43 | end 44 | 45 | def will_animate_rotate(_orientation, _duration) 46 | reapply_styles 47 | end 48 | end 49 | 50 | ``` 51 | 52 | 修改 `app/stylesheets/job_screen_stylesheet.rb` 53 | 54 | 加入 55 | 56 | ``` ruby 57 | 58 | class JobScreenStylesheet < ApplicationStylesheet 59 | # Add your view stylesheets here. You can then override styles if needed, 60 | # example: include FooStylesheet 61 | 62 | def setup 63 | # Add stylesheet specific setup stuff here. 64 | # Add application specific setup stuff in application_stylesheet.rb 65 | end 66 | 67 | def job_content(st) 68 | st.frame = { top: 100, left: 20, width: 100, height: 30 } 69 | st.color = color.black 70 | end 71 | 72 | def job_image(st) 73 | st.frame = { top: 200, left: 10, width: 200, height: 200 } 74 | st.background_color = color.black 75 | end 76 | 77 | def root_view(st) 78 | st.background_color = color.white 79 | end 80 | end 81 | 82 | 83 | ``` 84 | 85 | 86 | 修改 `app/models/job.rb` 加入 image_url 一個欄位 87 | 88 | 89 | ``` ruby 90 | 91 | class Job 92 | attr_accessor :id, :title, :content, :image_url 93 | 94 | def initialize(data) 95 | @id = data['id'] 96 | @title = data['title'] 97 | @content = data['content'] 98 | @image_url = data['image_url'] 99 | end 100 | 101 | # .... 102 | end 103 | ``` 104 | 105 | ## Step 4 : 106 | 107 | * rake 108 | 109 | 最後會生成 110 | 111 | ![](https://cdn.filepicker.io/api/file/7z35aRxfRriQO0dZXBKK) 112 | -------------------------------------------------------------------------------- /ios-guide-03.md: -------------------------------------------------------------------------------- 1 | # 目標: 2 | 3 | * 可以登入 4 | 5 | ![](https://cdn.filepicker.io/api/file/RbslfTbSQlCUzbQB3XKq) 6 | 7 | ![](https://cdn.filepicker.io/api/file/aP3GD5cERGqdvHSwwjD8) 8 | 9 | 10 | ![](https://cdn.filepicker.io/api/file/psnyJGyjRyruxfjDo4KQ) 11 | 12 | 13 | # 步驟: 14 | 15 | ## Step 1 : 16 | 17 | 修改 Gemfile 18 | 19 | 加入 20 | 21 | ``` ruby 22 | gem "motion-authentication" 23 | gem "ProMotion-XLForm" 24 | ``` 25 | 26 | * bundle install 27 | * rake pod:install 28 | 29 | ## Step 2 : 30 | 31 | 修改 `app/screens/home_screen.rb` 32 | 33 | 修改 `on_road` 34 | 35 | ``` ruby 36 | 37 | def on_load 38 | if Auth.signed_in? 39 | set_nav_bar_button :right, title: "Logout", action: :sign_out_button 40 | else 41 | set_nav_bar_button :right, title: "Sign In", action: :sign_in_button 42 | end 43 | 44 | @jobs = [] 45 | load_jobs 46 | end 47 | 48 | ``` 49 | 50 | 加入 51 | 52 | ``` ruby 53 | def sign_out_button 54 | Auth.sign_out do 55 | open_tab_bar HomeScreen.new(nav_bar: true) 56 | end 57 | end 58 | 59 | def sign_in_button 60 | open SignInScreen.new(nav_bar: true) 61 | end 62 | 63 | ``` 64 | 65 | ## Step 3 : 加入 SignInScreen 66 | 67 | * potion g screen sign_in 68 | 69 | 70 | ``` ruby 71 | 72 | class SignInScreen < PM::XLFormScreen 73 | title "Sign In" 74 | stylesheet SignInScreenStylesheet 75 | 76 | def form_data 77 | [ 78 | { 79 | cells: [ 80 | { 81 | title: "Email", 82 | name: :email, 83 | type: :email, 84 | placeholder: "Enter your email", 85 | required: true 86 | }, 87 | { 88 | title: "Password", 89 | name: :password, 90 | type: :password, 91 | placeholder: "Enter your password", 92 | required: true 93 | }, 94 | { 95 | title: "Sign In", 96 | name: :save, 97 | type: :button, 98 | on_click: lambda do |_cell| 99 | authenticate 100 | end 101 | } 102 | ] 103 | } 104 | ] 105 | end 106 | 107 | def authenticate 108 | Auth.sign_in(email: values["email"], password: values["password"]) do |response| 109 | if response.success? 110 | ApiClient.update_authorization_header(Auth.authorization_header) 111 | app.delegate.open_authenticated_root 112 | elsif response.object 113 | app.alert response.object["error"] 114 | else 115 | app.alert "Sorry, there was an error. #{response.error.localizedDescription}" 116 | end 117 | end 118 | end 119 | 120 | def will_animate_rotate(_orientation, _duration) 121 | reapply_styles 122 | end 123 | end 124 | 125 | ``` 126 | 127 | ## Step 4 : 新增 auth 128 | 129 | * touch `app/models/auth.rb` 130 | 131 | 132 | ``` ruby 133 | class Auth < Motion::Authentication 134 | strategy DeviseTokenAuth 135 | sign_in_url "http://localhost:3000/users/sign_in" 136 | end 137 | 138 | ``` 139 | 140 | ## Step 5 : compile 141 | 142 | * rake 143 | -------------------------------------------------------------------------------- /ios-guide-04.md: -------------------------------------------------------------------------------- 1 | # 做抽屜 2 | 3 | ![](https://cdn.filepicker.io/api/file/ZbQas77vSHCOlghBDYBn) 4 | 5 | # 步驟: 6 | 7 | ## Step 1: 8 | 9 | 安裝 Gem 10 | 11 | ``` ruby 12 | 13 | gem "ProMotion-menu" 14 | gem "motion-fontawesome" 15 | 16 | ``` 17 | 18 | * bundle install 19 | * rake pod:install 20 | 21 | ## Step 2 : 22 | 23 | * 修改 `app/screens/home_screen.rb` 24 | 25 | 26 | ``` ruby 27 | 28 | class HomeScreen < PM::TableScreen 29 | title "職缺一覽" 30 | stylesheet HomeScreenStylesheet 31 | 32 | include NavigationHelper 33 | 34 | def on_load 35 | add_side_menu 36 | 37 | @jobs = [] 38 | load_jobs 39 | end 40 | 41 | def sign_out_button 42 | Auth.sign_out do 43 | app.delegate.open_authenticated_root 44 | end 45 | end 46 | 47 | def sign_in_button 48 | open SignInScreen.new(nav_bar: true) 49 | end 50 | 51 | ``` 52 | 53 | 54 | ## Step 3 : 55 | 56 | * touch app/helpers/navigation_helpper.rb 57 | 58 | ``` ruby 59 | module NavigationHelper 60 | def add_side_menu 61 | label = UILabel.alloc.initWithFrame([[0, 0], [20, 20]]) 62 | label.font = FontAwesome.fontWithSize(16.0) 63 | label.text = FontAwesome.icon("list") 64 | label.color = rmq.color.black 65 | 66 | set_nav_bar_button :left, custom_view: label, action: :tapped_left_nav 67 | 68 | rmq(label).on(:tap) do |_sender| 69 | app_delegate.menu.show(:left) 70 | end 71 | end 72 | 73 | def tapped_left_nav 74 | app_delegate.show_menu 75 | end 76 | end 77 | 78 | ``` 79 | 80 | 81 | ## Step 4: 82 | 83 | 修改 `app/app_delegate.rb` 84 | 85 | ``` ruby 86 | 87 | class AppDelegate < PM::Delegate 88 | include CDQ # Remove this if you aren't using CDQ 89 | 90 | status_bar true, animation: :fade 91 | 92 | # Without this, settings in StandardAppearance will not be correctly applied 93 | # Remove this if you aren't using StandardAppearance 94 | ApplicationStylesheet.new(nil).application_setup 95 | 96 | def on_load(_app, _options) 97 | cdq.setup # Remove this if you aren't using CDQ 98 | open_authenticated_root 99 | end 100 | 101 | def show_menu 102 | @menu.show :left 103 | end 104 | 105 | # Remove this if you are only supporting portrait 106 | def application(_application, willChangeStatusBarOrientation: new_orientation, duration: duration) 107 | # Manually set RMQ's orientation before the device is actually oriented 108 | # So that we can do stuff like style views before the rotation begins 109 | device.orientation = new_orientation 110 | end 111 | 112 | def open_authenticated_root 113 | open_tab_bar HomeScreen.new(nav_bar: true) 114 | @menu = open MenuDrawer 115 | end 116 | end 117 | 118 | ``` 119 | 120 | ## Step 5 : 121 | 122 | * touch app/drawers/menu_drawer.rb 123 | 124 | ``` ruby 125 | class MenuDrawer < PM::Menu::Drawer 126 | def setup 127 | self.center = HomeScreen.new(nav_bar: true) 128 | self.left = NavigationScreen 129 | self.to_show = [:pan_bezel, :pan_center] 130 | self.transition_animation = :slide 131 | self.max_left_width = 200 132 | self.shadow = false 133 | end 134 | end 135 | ``` 136 | 137 | ## Step 6 : 138 | 139 | * rake 140 | -------------------------------------------------------------------------------- /more-repo.md: -------------------------------------------------------------------------------- 1 | # 如何找到更多開源示範教材 2 | 3 | * 打開 github 4 | * 使用 github 代碼搜尋功能 5 | * 搜尋 ProMotion 6 | * 切到 Ruby 分頁 7 | 8 | ## 寫得不錯的 Repo 9 | 10 | * https://github.com/notch8/calagator-ios 11 | * https://github.com/andrewhavens/railsconf-2016-demo-mobile-app 12 | 13 | 14 | ## Additional Resources 15 | 16 | * [RubyMotion Slack](https://motioneers.herokuapp.com) 17 | * [RubyMotion Community Forum](http://community.rubymotion.com) 18 | * [Portland RubyMotion Meetup Blog](http://motionpdx.github.io) 19 | -------------------------------------------------------------------------------- /rails-guide-01.md: -------------------------------------------------------------------------------- 1 | # 產生 API 所需資料 2 | 3 | * rails g scaffold job title:string content:text 4 | 5 | ## 使用 ActiveRecordSerializers 取代掉原本的 jbuilder 6 | 7 | * https://github.com/rails-api/active_model_serializers 8 | 9 | ``` ruby 10 | gem "active_model_serializers" 11 | ``` 12 | 13 | 生成 `app/serializers/job_serializer.rb` 14 | 15 | 內容 16 | 17 | ``` ruby 18 | class JobSerializer < ActiveModel::Serializer 19 | attributes :id, :title, :content, :image_url 20 | end 21 | ``` 22 | 23 | 24 | ## 檢驗是否有成功 25 | 26 | * 訪問 http://localhost:3000/jobs 27 | * (記得在 db/seed.rb 先塞點假資料) 28 | -------------------------------------------------------------------------------- /rails-guide-02.md: -------------------------------------------------------------------------------- 1 | # 讓 Rails 支援 API 登入 2 | 3 | ## Step 1 : 安裝 Devise 4 | 5 | * rails g devise:install 6 | * rails g devise user 7 | * rake db:migrate 8 | 9 | ## Step 2 : 新增認證 Token 到 user 10 | 11 | * rails g migration AddAuthTokenToUsers 12 | 13 | ``` ruby 14 | class AddAuthTokenToUsers < ActiveRecord::Migration[5.0] 15 | def change 16 | add_column :users, :authentication_token, :string 17 | add_index :users, :authentication_token 18 | end 19 | end 20 | 21 | ``` 22 | 23 | * rake db:migrate 24 | 25 | ## Step 3 : 修改 user model 26 | 27 | 28 | ``` ruby 29 | 30 | class User < ApplicationRecord 31 | # Include default devise modules. Others available are: 32 | # :confirmable, :lockable, :timeoutable and :omniauthable 33 | devise :database_authenticatable, :registerable, 34 | :recoverable, :rememberable, :trackable, :validatable 35 | 36 | before_save do 37 | if authentication_token.blank? 38 | self.authentication_token = generate_authentication_token 39 | end 40 | end 41 | 42 | def generate_authentication_token 43 | loop do 44 | token = Devise.friendly_token 45 | break token unless User.where(authentication_token: token).first 46 | end 47 | end 48 | end 49 | 50 | ``` 51 | 52 | ## Step 4 : 修改 application_controller 關掉 CSRF 53 | 54 | 55 | 56 | ``` ruby 57 | class ApplicationController < ActionController::Base 58 | protect_from_forgery with: :null_session 59 | 60 | before_action :authenticate_user_from_token! 61 | 62 | def authenticate_user_from_token! 63 | authenticate_with_http_token do |token, options| 64 | user = User.find_by(email: options.fetch(:email)) 65 | if user && Devise.secure_compare(user.authentication_token, token) 66 | sign_in user, store: false 67 | end 68 | end 69 | end 70 | end 71 | 72 | ``` 73 | 74 | 75 | ## Step 5 : 使用客製 sessions_controller 76 | 77 | * rails g controller sessions 78 | 79 | 80 | ``` ruby 81 | class SessionsController < Devise::SessionsController 82 | respond_to :json 83 | 84 | def create 85 | super do |user| 86 | data = { 87 | token: user.authentication_token, 88 | email: user.email 89 | } 90 | render(json: data, status: 201) && return 91 | end 92 | end 93 | end 94 | ``` 95 | 96 | ## Step 6 : 修改 config/routes.rb 97 | 98 | 99 | ``` ruby 100 | Rails.application.routes.draw do 101 | devise_for :users, controllers: { sessions: "sessions" } 102 | # ..... 103 | end 104 | 105 | ``` 106 | -------------------------------------------------------------------------------- /resource.md: -------------------------------------------------------------------------------- 1 | # 資源簡介 2 | 3 | ## RedPotion 4 | 5 | RedPotion 是集成了 RMQ, ProMotion, CDQ, AFMotion, MotionPrint 以及許多 Library 的一套 template,可以快速的搭建 rubymotion App 6 | 7 | * 官方文檔 http://docs.redpotion.org/en/latest/ 8 | 9 | ## ProMotion 10 | 11 | * https://github.com/infinitered/ProMotion 12 | 13 | ProMotion 提供了各式各樣的 "Screen" 以及「框架」。你除了可以使用 ProMotion 提供的 Screen 快速拉出版面外,還可以使用以下的 Gem 做出更多進階的效果。 14 | 15 | 比如說使用 `ProMotion-XLForm` 快速實現表單設計 16 | 17 | ``` ruby 18 | 19 | gem "ProMotion-XLForm" 20 | gem "ProMotion-push", "~> 0.2" # Push Notifications 21 | gem "ProMotion-map", "~> 0.3" # PM::MapScreen 22 | gem "ProMotion-iap" # PM In-app purchases 23 | gem "ProMotion-menu" # PM Side menu 24 | 25 | ``` 26 | 27 | ## RMQ 28 | 29 | * http://rubymotionquery.com/ 30 | 31 | 提供了類似 jQuery 的語法,可以讓開發者以類似「DOM 操作」的方式,進行 iOS 手機排版以及特效實踐。 32 | 33 | 在排版上非常省時。 34 | 35 | ## CDQ 36 | 37 | * https://github.com/infinitered/cdq 38 | 39 | 提供了一套 `ActiveRecord` like 的語法,支援讓開發者寫出易維護的語法,操作 CoreData ( 等同 iOS 裡的 database ) 40 | 41 | 其中的 Schema 機制類似於 db migration 42 | 43 | ## AFMotion 44 | 45 | https://github.com/clayallsopp/afmotion 46 | 47 | 是 AFNetworking 的 RubyMotion 版 wrapper。方便操作 API 48 | 49 | 50 | ## MotionToolbox 51 | 52 | * http://motion-toolbox.com/ 53 | 54 | 可以在上面找到你想要的第三方元件工具 55 | --------------------------------------------------------------------------------