MAC OSX Mavericks のrvmですこしはまる。

ブログなんて2年ぶりだわん。

 

MAC OSX の  Mavericks で ruby 1.9.3 を rvm 上で使おうとしたらはまったのでメモ。

 

ふつうにやってruby 1.9.3のインストールとかまではできたのだけど、

ネイティブビルドの必要な mysql2 gem を入れようとしたらこけてしまう。

$ gem install mysql2 -- --with-mysql-config=/usr/local/mysql/bin/mysql_config

Building native extensions with: '--with-mysql-config=/usr/local/mysql/bin/mysql_config'

This could take a while...

ERROR:Error installing mysql2:

ERROR: Failed to build gem native extension.

 

/Users/nyaago/.rvm/rubies/ruby-1.9.3-p429/bin/ruby extconf.rb --with-mysql-config=/usr/local/mysql/bin/mysql_config

checking for rb_thread_blocking_region()... yes

checking for rb_wait_for_single_fd()... yes

checking for rb_hash_dup()... yes

checking for rb_intern3()... yes

checking for mysql.h... no

checking for mysql/mysql.h... no

-----

mysql.h is missing.please check your installation of mysql and try again.

-----

*** extconf.rb failed ***

それで、経過が出力されている mkmf.log というファイルをみると、

"/usr/local/Cellar/gcc46/4.6.4/bin/gcc-4.6 -E -I/Users/nyaago/.rvm/rubies/ruby-1.9.3-p429/include/ruby-1.9.1/x86_64-darwin13.0.0 -I/Users/nyaago/.rvm/rubies/ruby-1.9.3-p429/include/ruby-1.9.1/ruby/backward -I/Users/nyaago/.rvm/rubies/ruby-1.9.3-p429/include/ruby-1.9.1 -I. -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE   -I/usr/local/opt/libyaml/include -I/usr/local/opt/readline/include -I/usr/local/opt/libksba/include -I/usr/local/opt/openssl/include -I/usr/local/mysql/include  -g -Os -arch x86_64 -fno-common   -D_P1003_1B_VISIBLE -DSIGNAL_WITH_VIO_CLOSE -DSIGNALS_DONT_BREAK_READ -DIGNORE_SIGHUP_SIGQUIT  -DDONT_DECLARE_CXA_PURE_VIRTUAL  -O3 -ggdb -Wall -Wextra -Wno-unused-parameter -Wno-parentheses -Wno-long-long -Wno-missing-field-initializers -Wpointer-arith -Wwrite-strings -Wdeclaration-after-statement -Wimplicit-function-declaration  -fno-common -pipe  conftest.c -o conftest.i"

 

gcc-4.6: error: x86_64: No such file or directory

 x86_64 がないといかいわれている。コンパイラさんが '-arch x86_64 ' というのを理解していないということのようですね。

なんか、ruby 1.9.3 をインストールしようとしたところで、gcc46 というのが必要ということでHomebrewでそれをインストールして使っているようなんだけど、その gcc46 は '-arch 'という指定を受け付けないということのよう。

ということで、OSの標準コンパイラが使用されるようにしてみようということになり、

OS標準のフロントコンパイラ(clang )を使ってruby 1.9.3をリインストールしてから mysql2 の gem をインストールしてみた。

$ rvm reinstall 1.9.3 --with-gcc=clang

 

......................

 

$ rvm 1.9.3

 

$rvm 1.9.3

nyaago@mumichan: 541 $gem install mysql2

Building native extensions.  This could take a while...

Successfully installed mysql2-0.3.13

Installing ri documentation for mysql2-0.3.13

 

1 gem installed

これで、多分OKかな。まだ動作はさせてないけど。

CRUDのRouting 設定とHelperメソッド

いろいろとひっかかてしまうので、
Rails3 のCRUDのRouting 設定と、その設定によって生成されるHelperメソッドの関連について整理してみました。
sites という コントローラーを例にしています。

Routingの定義

routes.rb で (ActionDispatch::Routing::Mapper::Resources の) resources メソッドによって、URLとそれを処理するコントローラー、アクションの対応づけが生成されます。

Hoge::Application.routes.draw do
  resources :sites

..
end


この設定後、rake routes を実行すると、以下のように、URL及びHTTPメソッド(GET/POST/PUT/DELETE)と、それを処理するコントローラー/アクションのRouting設定ができていることが確認できます。

$cd <application path>
$rake routes
         sites GET        /sites(.:format)                    {:action=>"index", :controller=>"sites"}
               POST        /sites(.:format)                    {:action=>"create", :controller=>"sites"}
      new_site GET     /sites/new(.:format)            {:action=>"new", :controller=>"sites"}
     edit_site GET      /sites/:id/edit(.:format)       {:action=>"edit", :controller=>"sites"}
          site GET        /sites/:id(.:format)               {:action=>"show", :controller=>"sites"}
               PUT          /sites/:id(.:format)               {:action=>"update", :controller=>"sites"}
               DELETE     /sites/:id(.:format)               {:action=>"destroy", :controller=>"sites"}

create(新規作成)の場合は、HTTPメソッドが'POST', update(変更)の場合は'PUT'、destroy(削除)の場合は'DELETE'となるようマッピングされます。

生成されるHelper

上記のようにルーティングの定義を行うと以下のメソッドが自動的に生成されます。

site_path(site, options)

/sites/ というURLパスを返す。optionsには、ハッシュでリクエストパラメーターでわたすキーと値を指定。

new_sites_path(options)

/sites//new というURLパスを返す。optionsには、ハッシュでリクエストパラメーターでわたすキーと値を指定。

edit_site_path(site,options)

/sites/edit/ というURLパスを返す。optionsには、ハッシュでリクエストパラメーターでわたすキーと値を指定。

sites_path(options)

/sites というURLパスを返す。optionsには、ハッシュでリクエストパラメーターでわたすキーと値を指定。

viewでのリンク出力でのHelperの使い方

edit や destroy のリンクを作る場合の例は、以下のようになります。

editリンクの場合
link_to 'edit' ,edit_site_path(site, :page => 1)
=>
<a href="/sites/4/edit?page=1">edit</a>
destroyリンクの場合。/sites/ のパスを、Httpメソッドを'DELETE'にしてのリクエストになります。
link_to 'destroy',
    site_path(site, :page => 1), 
    :confirm =>  'Are you sure?', 
    :method => :delete
=>
<a href="/sites/1?page=1" data-confirm="Are you sure?" data-method="delete" rel="nofollow">destroy</a>
indexの一覧ページ。
link_to 'index', sites_path
=>
<a href="/sites">index</a>

form でのつかわれかた

form_for Helper メソッドで、以下のように、モデルレコードを引数に指定してやると、update または、create のための form が出力されます。

<%= form_for(@site) do |f| %>

..

<% end %>
update が生成される場合

edit action で form_forが使われた場合、site_path メソッドが内部的に呼び出されて、updateのためのformを出力されます。以下のように、urlが/sites/ で Httpメソッドが'PUT'となるformとなります。

<form accept-charset="UTF-8" action="/sites/1" class="edit_site" id="edit_site_1" method="post">
create が生成される場合

new action で form_forが使われた場合、sites_path メソッドが内部的に呼び出されて、createのためのformを出力されます。以下のように、urlが/sites で Httpメソッドが'POST'となるformとなります。

<form accept-charset="UTF-8" action="/sites" class="new_site" id="new_site" method="post"

とりあえず、以上、標準的、単純な場合の使いかたについて。
あと、URLパターンなどが、違う場合(CRUDでない場合、レコードのid以外をurlのパターンに含む場合など)に、うまくLinkやFormがつくれるやりかたもかんがえないと..

OpenGL ESでの画角の指定

iPhone 上でOpen GL ES1を利用して、画角を指定してのオブジェクトの描画方法を調べてみまたので、
メモです。
(もっと、きちんとした方法が他にあるのだとうとはおもいますが....)

前提準備

  1. XCodeでOpen GL ES Applicationを選択してプロジェクトを作成。これによって、テンプレートソースが生成される。
  2. OpenGL ES 2.0 対応を無効にする

EAGLView.m の initWithCoder メソッドでのES2Rendererオブジェクトの生成部分をコミットするだけ.

// renderer = [[ES2Renderer alloc] init];

これで、ES1Renderer.m の renderメソッドを編集することにより、
Open GL ES1 での描画をすることができる。

ここらはこちらなどを参照.

描画実装の前提

とりあえず、簡略可するために以下の制限、前提での実装としました。

  • 幅->1.0, 高さ->1.0 / (Window の幅 / 高さ), 奥行き1.0のサイズのオブジェクト空間に描画することとする。座標としては、x -> -0.5〜 0.5, y -> -0.5 * (Window の幅 / 高さ)〜0.5 * (Window の幅 / 高さ), z -> -0.5 〜0.5の

範囲のオブジェクトが描画されるものとする。

  • 水平画角での計算とする。

実装

画角/2に対するtanの逆(余接)にObject座標の大きさ(1.0)を掛けたものが、表示範囲(視体積)の一番手前となり、
かつ、オブジェクトの手前になるように実装しました。
手順としては。

  1. 画角/2に対するtangentの逆(余接)にObject座標の大きさ(1.0)を掛けたものが、表示範囲(視体積)の一番手前となるよう、glFrustumfでの設定を行う。
  2. 描画オブジェクトを原点から表示範囲の手前までの距離とObject座標上のZ座標のマイナス方向の大きさを加えた距離だけマイナス方向に移動.これで、Objectのマイナス側の一番手前が、表示範囲(視体積)の一番手前となる.


ES1Renderer.m の renderメソッドの実装は以下のとおりです。画角は60度とベタに指定しています。

- (void)render {
  // This application only creates a single context which is already set current at this point.
  // This call is redundant, but needed if dealing with multiple contexts.
  [EAGLContext setCurrentContext:context];
 
  // This application only creates a single default framebuffer which is already bound at this point.
  // This call is redundant, but needed if dealing with multiple framebuffers.
  glBindFramebufferOES(GL_FRAMEBUFFER_OES, defaultFramebuffer);
  glViewport(0, 0, backingWidth, backingHeight);

 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  GLfloat fovy = 60.0f;  // 画角を60度とする
  /*
       角度にあわせた視体積の設定.
        tanの逆(余接)にObject座標の大きさを掛けたものが、
    nearの位置にくるようにする.
  */
  GLfloat aspect = (GLfloat)backingWidth / (GLfloat)backingHeight;
  GLfloat width = 1.0f;
  GLfloat height = width / aspect;
      GLfloat near = 1.0f;
  GLfloat far = 100.0f;
  near = 1.0f / tan(fovy * 0.5 * M_PI / 180.0) * width;
  GLfloat x = width / 2;
  GLfloat y = height / 2;
  glFrustumf(-x, x, -y, y, near, far);
     NSLog(@"glFrustumf(%f,%f,%f,%f,%f,%f)",-x, x, -y, y, near, far);
  /*
    視体積のnearの位置にあわせて、モデルZ座標を移動
    原点からnearまでの距離+ObjectのZ座標のマイナス方向への大きさ分だけマイナス方向に移動.
    これで、Objectのマイナス側の一番手前が、
    視体積の一番手前となる.
  */
  //
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0f, 0.0f, -(width / 2) - near);
   NSLog(@"glTranslatef z = %f",-(width / 2) - near);
  // .... ここでオブジェクトの描画を....
   // [self draw];
  // 
  // This call is redundant, but needed if dealing with multiple renderbuffers.
  glBindRenderbufferOES(GL_RENDERBUFFER_OES, colorRenderbuffer);
  [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

scrollView内にImageViewを配置する。

iPhoneアプリで、scrollView内に任意の縦横比のImageViewをセンタリングして配置するための実装を行ってみる。

scrollviewの生成

前提となる準備として view load時にscrollViewの生成,セットアップを行う。

- (void)viewDidLoad {
  NSLog(@"photo view contoller viewDidLoad");
  [super viewDidLoad];
  
  .... あれこれ ....

  CGRect rect = self.view.bounds;
  scrollView = [[UIScrollView alloc] initWithFrame:rect];
 
  scrollView.maximumZoomScale = 3.0;
  scrollView.minimumZoomScale = 1.0;
  scrollView.delegate = self;
  scrollView.scrollEnabled = YES;
  scrollView.userInteractionEnabled = YES;
  scrollView.bounces = NO;
  scrollView.multipleTouchEnabled = YES;
  [self.view addSubview:scrollView];
}

ImageViewを追加

ImageViewを追加する(Viewが表示されるタイミングなど)さいには、以下のようにセンタリングされるようview frameの開始座標を設定すればよい。

- (void)viewDidAppear:(BOOL)animated {

  [super viewDidAppear:animated];

  .... あれこれ .....

  CGSize size = self.imageView.image.size;
  CGRect viewRect = self.scrollView.bounds;
  CGRect rect;
  if(size.height / size.width > viewRect.size.height / viewRect.size.width ) {
    // 縦長
    float rate = viewRect.size.height / image.size.height;
    float width = size.width * rate;
   
    rect = CGRectMake((viewRect.size.width -  width) / 2,
                      0.0f,
                      width,
                      viewRect.size.height);
  }
  else { // 横長
    float rate = viewRect.size.width / image.size.width;
    float height = size.height * rate;
    rect = CGRectMake(0.0f,
                      (viewRect.size.height - height) / 2,
                      viewRect.size.width,
                      height);
  }
  imageView.frame = rect;
}

Zooming時の座標調整

Zooming時にもセンタリングされるようview frameの開始座標を調整してやる必要がある。
scrollViewのDelegate(UIScrollViewDelegateプロトコル)の- (void)scrollViewDidZoom:(UIScrollView *)scrollView
メソッドで行う。

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  CGRect frame = imageView.frame;
  if( (self.scrollView.bounds.size.width -
       self.scrollView.contentSize.width) / 2 > 0) {
       frame.origin.x = (self.scrollView.bounds.size.width -
                      self.scrollView.contentSize.width) / 2;
  }
  else {
    frame.origin.x = 0.0f;
  }
  if((self.scrollView.bounds.size.height -
      self.scrollView.contentSize.height) / 2 > 0) {
       frame.origin.y = (self.scrollView.bounds.size.height -
                      self.scrollView.contentSize.height) / 2;
  }
  else {
    frame.origin.y = 0.0f;
  }
 
  imageView.frame = frame;

}

Zooming/ScrollされるViewの指定も忘れずに

もちろん、Delegate(UIScrollViewDelegateプロトコル)の- viewForZoomingInScrollView:メソッドでScrollView内に含めるimageViewの指定を行っておく必要がある。

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  return self.imageView;
}

手続きの抽象化

sicp の1.3.3 と1.3.4 を復習。general Methodと手続きを返す手続きをつかっての手続きの抽象化。題材は主に、平方根で。

[1.3.3 general Methodを使っての抽象化]

指定した手続きの入力と出力の差が十分小さくなくなる時点(fixed point)の入力値を得るための手続き fixed-pointを定義。

(define tolerance 0.00001)
(define (fixed-point f first-guess)
  (define (close-enough? x y)
    (< (abs (- x y) ) tolerance) ) 
  (define (try guess)
    (let ((next (f guess) ) )
       (if (close-enough? next guess)
           next
           (try  next) ) ) ) 
  (try first-guess) )

この手続きを使って。平方根を返す手続きを定義。
算出値の初期値 1.0として
((平方根算出対象の値/前回の算出値) と前回算出値の平均値)の算出を繰り返し実行。 fixed point(前回と今回の算出値の差が十分小さくなる時点)の算出値を平方根の結果とする。

(define (sqrt x)
  (fixed-point (lambda (y) (average y (/ x y) ) ) 1.0) )

[1.3.4 手続きを返す手続き]

手続きを返す手続きを実装することにより、抽象化を行う。
指定した手続きの入力と出力の平均を求める手続きを返す手続き
その手続きを手続きの差が十分小さくなるときの出力値を求める手続き(fixed-point)にわたして平方根をとく
ここでは、以下の3つの手続きを組み合わせていることになる。

      1. x/yを求めるlambda
      2. 指定した手続きの入力と出力の平均を求める手続きを返す手続き(average-dump)
      3. 手続きの入力と結果の差が十分小さくなるときの出力値を求める手続き(fixed-point)
(define (average-dump f)
  (lambda (x) (average x (f x))))

(define (sqrt x)
  (fixed-point (average-dump (lambda (y) (/ x y) ) ) 1.0) )

さらに

さらに、上記の2の目の手続き(average-dump)から1つめの手続き(x/yを求めるlambda)を呼び出すのではなく、それぞれの手続き引数で渡すように抽象化すると。

(define (fixed-point-of-transform g transform guess)
  (fixed-point  (transform g) guess) )

(define (sqrt x)
  (fixed-point-of-transform (lambda (y)  (/ x y) )
                            average-dump

addSubViewで追加されるタイミングでsub Viewを表示

課題

iphoneでViewが表示されるときに下位にViewやControlを追加するときのことです。viewDidAppear:でaddSubViewを使ってViewやControlを追加すると、viewDidAppearの処理が完了してから、UiKitによって一括して描画が行われることになります。こうすると下位の要素数が多い場合などは、描画が始めるまで時間がかかってしまうことになる。そのため、最後に一括して、描画されるのではなくaddSubViewでView階層に追加されるたびに表示したい場合があります。

解決方法

まず、下位のView,Controlを追加するための処理は、UIKitの描画処理と非同期に行われるようにviewDidAppear:から新規スレッドで起動されるようにします。具体的にはNSThreadを使うことになります。
その別スレッドで、下位のView,Controlを追加していくのだが、その別起動されているスレッドでaddSubViewしても、もちろん、描画の処理は起動されない。そこで、描画を行うスレッド、つまりメインスレッドでaddSubViewを行う必要があります。そこで、個々のaddSubViewを行う別メソッドを作成、NSObjectのperformSelectorOnMainThread:withObject:waitUntilDoneメソッドを使って、それがメインスレッドで起動されるようにします。

それで、具体的のどのような実装になるかが以下です。

viewDidAppear:では、以下のとおり、NSThreadの detachNewThreadSelector:toTaarget:withObjectなどにより、view追加のメソッドを別スレッドで呼び出します。

- (void) viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];


 
  [NSThread detachNewThreadSelector:@selector(afterViewDidAppear:)


                           toTarget:self
                         withObject:nil];
}


別スレッドで起動される、メソッド内では、view。そのviewが、メインスレッドで追加されるように、NSObjectのperformSelectorOnMainThread:withObject:waitUntilDoneメソッドにより、実際のview追加を行うメソッドを呼び出します。

- (void) afterViewDidAppear:(id)arg { 
 
  for(NSUInteger i = 0; i < [self subViewCount]; ++i) {


    UIView *subView = [self subViewAt:i];
    if(imageView) {
      [self performSelectorOnMainThread:@selector(addView:)
                             withObject:subView
                          waitUntilDone:NO];
  }
}


実際にview,controlの追加を行うメソッドは、単純な場合は、以下のように単にaddSubViewを呼び出すだけとなります。

- (void)addView:(id)subView {
  [self.view addSubview:subView];
}

Barの表示/非表示切り替え(navigationView-scrollView構成)

navigationView の下にscrollViewがあるという構成の場合に
iphone - navigationBar,statusBar,toolBarのOn/Offがされるようにする。表示がOnの場合にも、全画面表示でnavigationBar,statusBar,toolBarが半透明である状態にする。という実装をいろいろなやみながら実装してみました。

初期表示

まず,Viewが表示される前に(=UIViewControllerのviewWillAppear)では、全画面表示をすること(UIViewControllerのwantsFullScreenLayout)の設定を行います

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  // 全画面表示
  self.wantsFullScreenLayout = YES;
}

(以上 5/19追記)

つぎの,Viewが表示されるとき(=UIViewControllerのviewDidAppear)には、各Barに対して以下の設定を行い、それらが表示されないようにしました。

      1. Barの半透明のスタイル設定
      2. Barを非表示にする設定
      3. 全画面表示をすること(UIViewControllerのwantsFullScreenLayout)
- (void)viewDidAppear:(BOOL)animated {  [super viewDidAppear:animated];

   self.navigationController.toolbarHidden = YES;


  ....
  ScrollView内にView/UIControlの配置をする処理
  ....

  // Navigation Bar, Status Bar, ToolBarのスタイル(透明、黒)


  self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;
  [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
  self.navigationController.toolbar.barStyle = UIBarStyleBlack;
  self.navigationController.toolbar.translucent = YES;
  // Navigation Bar, Status Bar, ToolBarを非表示に
  [UIApplication sharedApplication].statusBarHidden = YES;
  self.navigationController.navigationBar.hidden = YES;
  self.navigationController.toolbarHidden = YES;
....
}

表示/非表示の切り替え

表示/非表示の切り替えには、上記と同様に各表示設定のプロパティ値を指定することにより行えるはず。ということで。

// 表示/非表示の反転
BOOL hidden = !self.navigationController.navigationBar.hidden;
self.navigationController.navigationBar.hidden = hidden;
[[UIApplication sharedApplication] setStatusBarHidden:hidden animated:YES];
[self.navigationController setToolbarHidden:hidden];

表示位置にずれへの対応

以上のようにして、表示の切り替えはできたのだけれど、navigationBarの分なのかtoolBarの分なのかわからないが、下方向に表示座標がずれてしまう。調べてみると描画後にScrollViewの内容を配置するためのInsetとOffset(contentInsetとcontentOffsetプロパティ)がずれてしまいます。そのため一旦UIKitの描画がされた後に、それらのプロパティを初期状態(offsetはx,yとも0,Insetも全て0に)に戻すようにします。その初期状態を戻す処理をメソッドとして定義して、NSObjectで定義されている[performSelectorOnMainThread:withObject:waitUntilDone]でそれを呼び出すことによって行います。

- (void)resetScrollOffsetAndInset:(id)arg {
  PageScrollView *scrollView = (PageScrollView *)self.view;
  UIEdgeInsets inset;
  inset.left = 0;
  inset.right = 0;
  inset.top = 0;
  inset.bottom = 0;
  scrollView.contentInset = inset;
  CGSize size = scrollView.contentSize;
  size.height = self.view.frame.size.height;
  scrollView.contentSize = size;
}


// 表示/非表示の反転
-(void) changeNavigationAndStatusBar {
  BOOL hidden = !self.navigationController.navigationBar.hidden;
  self.navigationController.navigationBar.hidden = hidden;
  [[UIApplication sharedApplication] setStatusBarHidden:hidden animated:YES];
  [self.navigationController setToolbarHidden:hidden];


  // scrollViewのcontentのoffsetとinsetを元の状態に戻す
  [self performSelectorOnMainThread:@selector(resetScrollOffsetAndInset:)
                            withObject:[NSNumber numberWithBool:hidden]
                        waitUntilDone:NO];
}

機器回転時など

機器回転時の対応をする場合なども表示/非表示の反転と同様にScrollViewのContentInsetとcontentOffsetを元に戻すために[performSelectorOnMainThread:withObject:waitUntilDone]の呼び出しが必要でした。

まだまだ課題

初期状態でBarを表示すると、位置がずれてしまうなど、まだ問題ありです。