UIImageViewを丸くトリミングする

TweetBotのサムネイル表示のように、画像を丸くトリミングする方法を紹介します。
CALayerを使うと数行で実装出来るので便利です。

とりあえずサンプルコード

UIImage *circleImage = [UIImage imageNamed:@"test.png"];
UIImageView *circleImageView = [[UIImageView alloc] initWithImage:circleImage];
if (circleImage.size.width != circleImage.size.height) {
    CGFloat smallerSideLength = (circleImage.size.width < circleImage.size.height) ? circleImage.size.width : circleImage.size.height;
    circleImageView.frame = CGRectMake((circleImage.size.width - smallerSideLength) * 0.5f,
                                       (circleImage.size.height - smallerSideLength) * 0.5f,
                                       circleImage.size.width, circleImage.size.height);
} else {
    circleImageView.frame = CGRectMake(0.0f, 0.0f, circleImage.size.width, circleImage.size.height);
}
circleImageView.layer.cornerRadius = circleImageView.frame.size.width * 0.5f;
circleImageView.clipsToBounds = YES;
[self addSubview:circleImageView]

iOS Circler masked UIImageView


上のコードで、四角形のUIImageViewを正円にトリミング出来ます。
元の画像の縦横比が違うときは、短い辺を基準に、真ん中でトリミングしています。


大切なのは

circleImageView.layer.cornerRadius = circleImage.size.width * 0.5f;
circleImageView.clipsToBounds = YES;

この2行だけです。

元の画像としてtwitter等の正方形の画像を用意すれば、面倒な分岐も必要ないので、単純に円形に切り抜きたいという時に使えるかと。

google custom search engine(CSE)を使って、検索結果をjsonで取得する

google検索結果を取得しようとした時に、すこし苦労したので手順を整理しておく。


参照したのはここ
Custom Search — Google Developers

API keyの取得

https://cloud.google.com/console/project

まず、上のURLにアクセス

f:id:ryufloat:20140118163048p:plain

次に規約に同意するので Accept をクリック

f:id:ryufloat:20140118163944p:plain
①、②、③の順にクリックして


f:id:ryufloat:20140118163951p:plain
必要な種類のkeyを選択。
今回はServer Keyを作る。


f:id:ryufloat:20140118163956p:plain
IPアドレスで制限をかけたい場合は、枠の中に書く。
今回は制限を書けないので空のままCreateをクリック


f:id:ryufloat:20140118164007p:plain
これでAPI keyの作成が完了。


Custom search engine IDの作成

Custom Search Engine
上のURLにアクセス

f:id:ryufloat:20140118165433p:plain
Addをクリックして、Custom search engineの登録をする


f:id:ryufloat:20140118165440p:plain
Custom search engineは、本来自分のサイト内検索用のAPIなため、最低ひとつのURLを入れないと作成出来ない。
ここでは適当に入れておき、あとでコントロールパネルから削除を行う。


f:id:ryufloat:20140118165448p:plain
作成後、表示されるページでコントロールパネルへ


f:id:ryufloat:20140118165453p:plain
フィルタ等はこのページで指定出来る。
google 全体を検索したい場合、

  • ウェブ全体 を指定
  • 検索したいサイト を空欄に

するとOK。

Custom search engine IDは、このコントロールパネル内で確認出来る。

検索を行いJSONを取得する

https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM SEARCH ENGINE ID}&q={SEARCH_WORDS}

このURLを使う。
これは検索を行いJSONデータを返却するURLになっている。
なので、任意の言語でjsonからdecodeをして、使用すればおk。

パラメータを少しだけ解説

  • key: API key
  • cx: Custom search engine ID
  • p: 検索用クエリ

とりあえず、上の3つがあれば検索出来るはず。

JSONからサイトの情報を取得する

得られたJSONデータにおいて、itemsにサイトの情報が入っている。

後は実際にデータを眺めてもらえば良いと思うが、一応PHPの簡単なサンプルをおいておく。

このサンプルでは、得られたデータから、1番目に表示されるサイトの情報を取得するところまで行っている。

$cse_url = https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM SEARCH ENGINE ID}&q={SEARCH_WORDS}';
$search_result = file_get_contents($cse_url, true);
$search_result = json_decode($search_result);
$result_first = $search_result->items[0];

この先で取得出来るサイトの情報は、次の項に書いたパラメータが記載されているURLを参照してもらうと良いかと。

パラメータ

以下にパラメータが載っているので、何かやりたいと思ったら参照してみると良い。
CSE: list - Custom Search — Google Developers


注意点

検索用URLはSSLを使用”しなければならない”。

次のようにhttp://...とすると、

http://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM SEARCH ENGINE ID}&q={SEARCH_WORD}

以下のエラーを吐く

{"error":{"errors":[{"domain":"global","reason":"sslRequired","message":"SSL is required to perform this operation."}],"code":403,"message":"SSL is required to perform this operation."}}

これに関しては
error in getting userinfo from google drive api - Stack Overflow
ここにある通り。

httpではなくhttpsを使えばOK

https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM SEARCH ENGINE ID}&q={SEARCH_WORD}

Rails 4.0 で多対多関連

この記事内では、"timeline"モデルと,"user"モデルをどのようにして接続する事が出来るかを見ていきます。

timeline.user
user.timeline

等でアクセス出来るように作っています(つもりです)。

Rubyや、Railsの経験が浅いため、もっと良い方法を知っている方、間違えを見つけた方はコメントを頂けるととてもうれしいです。

またポリモーフィズムを使用した関連付けに関しては触れていません。

あくまで導入部分だと思って参照して頂ければとおもいます。

HABTM

has_and_belongs_to_many を使った例。

モデルをいくつかの種類に分けて参照させたいときなどに便利です。

#/app/models/user.rb
class User < ActiveRecord::Base
  has_and_belongs_to_many :timelines_member, :class_name => 'Timeline',
                          :foreign_key => :member_timeline_id, :association_foreign_key => :member_id,
                          :join_table => :timelines_members

  has_and_belongs_to_many :timelines_observer, :class_name => 'Timeline',
                          :foreign_key => :observer_timeine_id, :association_foreign_key => :observer_id,
                          :join_table => :timelines_observers
end
#/app/models/timeline.rb
class Timeline < ActiveRecord::Base
  has_and_belongs_to_many :members, :class_name => 'User',
                          :foreign_key => :member_id, :association_foreign_key => :member_timeline_id,
                          :join_table => :timelines_members

  has_and_belongs_to_many :observers, :class_name => 'User', 
                          :foreign_key => :observer_id, :association_foreign_key => :observer_timeine_id,
                          :join_table => :timelines_observers
end
#/db/migrate/[TIMESTAMP]_timelines_members.rb
class TimelinesMembers < ActiveRecord::Migration
  def change
    create_table :timelines_members, id: false do |t|
      t.integer :member_id, null: false
      t.integer :member_timeline_id, null: false
    end
    add_index(:timelines_members, [:member_id, :member_timeline_id], :unique => true)
  end
end
#/db/migrate/[TIMESTAMP]_timelines_obserbers.rb
class TimelinesObservers < ActiveRecord::Migration
  def change
    create_table :timelines_observers, id: false do |t|
      t.integer :observer_id, null: false
      t.integer :observer_timeline_id, null: false
    end
    add_index(:timelines_observers, [:observer_id, :observer_timeline_id], :unique => true)
  end
end

HM with relationship table

has_manyとbelongs_toで関連を作るのですが、2つのモデルの間にrelationshipテーブルを挟む方法です。

2つのモデルの間に”状態”等のステータスを持たせたい場合に有効です。

#/app/models/user.rb
  has_many :timeline_user_relationships
  has_many :timelines, :through => :timeline_user_relationships
end
#/app/models/timeline.rb
class Timeline < ActiveRecord::Base
  has_many :timeline_user_relationships
  has_many :users, :through => :timeline_user_relationships
end
#/app/models/timeline_user_relationship.rb
class TimelineUserRelationship < ActiveRecord::Base
  belongs_to :user
  belongs_to :timeline
end
#/db/migrate/[TIMESTAMP]_create_timeline_user_relationships.rb
class CreateTimelineUserRelationships < ActiveRecord::Migration
  def change
    create_table :timeline_user_relationships do |t|
      t.references :user, index: true
      t.references :timeline, index: true
      t.integer :role

      t.timestamps
    end
    add_index(:timeline_user_relationships, [:user_id, :timeline_id], :unique => true)
  end
end

create_join_table

Rails 4.0から追加されたものです。
Railsドキュメントにも記載されています。
http://railsdoc.com/migration#テーブルを結合して作成(create_join_table)

手軽に作成出来るので、とりあえず関連をつけたいという時に使うと良いかと思います。

#/app/models/user.rb
class User < ActiveRecord::Base
  has_and_belongs_to_many :timelines
end
#/app/models/timeline.rb
class Timeline < ActiveRecord::Base
  has_and_belongs_to_many :users
end
#/db/migrate/[TIMESTAMP]_create_timelines_users.rb
class CreateUsersGroups < ActiveRecord::Migration
  def change
    create_join_table :timelines, :users
    add_index(:groups_users, [:group_id, :user_id], :unique => true)
  end
end


ざっと3種類紹介しましたが、option等は必要になった際に使ってみて追加していこうと思います。

FrozenBear v1.4.0

f:id:ryufloat:20131219015205j:plain

先日、FrozenBear最後(?)の主要バージョンとなる、バージョン1.4.0をリリースしました。

昨年冬に最初のリリースを行い、季節の巡りとともに、FrozenBearのアップデートを重ねて参りましたが、ここで一区切りとなります。


二度目の冬は、明るい陽の光がきれいです。


末永くお使いいただける事を願っております。

rspec-rails (2.14.0) + devise (3.2.1, 3.2.0) + capybara (2.2.0) でsign in判定

deviseを使っていて、rspec内でサインインしているかどうかの判定をしたいときのやり方。

バージョンごとに微妙に変化があるようで、実際に動作確認したのはタイトルのもののみです。
他のバージョンに関しては、この記事では対応出来ない可能性があるため、注意してください。


1つずつ注意点を確認して行きます。
最終的な結論は記事の下の方にコードをおいておくので、やり方だけ分かれば良いという人は下までスキップしてください。

devise

deviseを使う際に問題になる事の1つに、rspecでのsign in、sign outの検証が、deviseが提供する’sign_in’と’sign_out'メソッドでは出来ないという事があります。

そこで、以下のページで解決策が提示されています。

https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

このページではまず、Capybaraによって提供されるメソッドを使って、Sign inフォームを直接埋めることでsign inする事を推奨しています。しかし推奨した後に、この方法ではテストの数が増えた時に時間がかかってしまうとも言っています。

そこで、warden test helpersというヘルパーを用いてこの問題を解決する方法が書かれています。

そして、なぜ’sign_in’と’sign_out’メソッドを使わないかというと、以下のような理由らしいです。

If you're wondering why we can't just use Devise's built in sign_in and sign_out methods, it's because these require direct access to the request object which is not available while using Capybara. To bundle the functionality of both methods together you can create a helper method.

翻訳(適当)すると
『なぜDeviseによって提供されている’sign_in’と’sign_out’メソッドを使わないのか疑問に思うだろう。その理由は、Capybaraを使っている時には使う事のできない”request object”に直接アクセスする必要があるから。なので、それら2つのメソッドを持っているヘルパーメソッドを自分で作ってやる必要がある。』
らしいです。

という事で、deviseの問題はwarden test helpersで解決する事が出来ます。

capybara

rails_tutorial(http://railstutorial.jp/?version=4.0#top)をやっていると、capybaraを使用しているspecが spec/requests というフォルダ内にある事に気がつくかもしれません。
そうして、capybara v2.2.0でも同じようにするとうまくいかなくてはまります。
2.2.0では仕様変更があったらしく、capybaraが動作するのは <b>spec/features</b> 内のファイルに対してとなっています。
ここを間違えていると、いくらdeviseの問題を解決してもテストが通りません。

また、Capybaraを使用するためには、rspecに Capybara::DSL をincludeする必要もあります。

結論

以上2つの問題を解決するよう定義したspec_helperが以下になります。

#spec/spec_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

RSpec.configure do |config|
  # ## Mock Framework
  #
  # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred
  # automatically. This will be the default behavior in future versions of
  # rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false

  # Run specs in random order to surface order dependencies. If you find an
  # order dependency and want to debug it, you can fix the order by providing
  # the seed, which is printed after each run.
  #     --seed 1234
  config.order = "random"

  # for Capybara : Capybara用のDSLをinclude
  config.include Capybara::DSL

  # for Devise : Deviseを使用している時にsign in/outを取るためのヘルパーをinclude
  include Warden::Test::Helpers
end 

さらに、capybaraは必ず spec/features フォルダー内で使用してください。
そうしないとvisitやpageが定義されていません、というようなエラーが出て、テストが赤くなります。


最後に、実際にログイン、ログアウトの処理の仕方を確認します。

login_as(user, :scope => :user)     #ユーザーをadmin falseでログイン
logout(:user)     #ログインしているユーザーをログアウトさせる

上記2つのメソッドで、ログイン、ログアウトの処理を実行します。
下にサンプルのspecファイルをおいておいたので、照らし合わせて確認してみてください。

#spec/features/application_controller_spec.rb  !!features 直下であることに注意
require 'spec_helper'
 
describe 'ApplicationController' do
 
  let(:user) { FactoryGirl.create(:user) }
 
  describe 'header' do
    
    describe 'has menus' do
 
      before { visit root_path }
 
      specify 'whenever a user is signed in or not' do
        expect(page).to have_link('Home', root_path)
      end
 
      specify 'when a user is signed in' do
        login_as(user, :scope => :user)
        visit root_path
 
        expect(page).to have_link('Sign out', destroy_user_session_path)
        expect(page).not_to have_link('Sign up', new_user_registration_path)
        expect(page).not_to have_link('Sign in', new_user_session_path)
      end
 
      specify 'when a user is signed in' do
        logout(:user)  
      
        expect(page).to have_link('Sign up', new_user_registration_path)
        expect(page).to have_link('Sign in', new_user_session_path)
        expect(page).not_to have_link('Sign out', destroy_user_session_path)
     end
    end
  end
end

rspec-rails (2.14.0) + devise (3.2.1, 3.2.0) + cap ...