人生の恥は書き捨て

プログラムとかいろいろ

NSTimerとUISliderを使ってSwiftで画像スライドショーを作る

やること

画像のスライドショーのようなものを作ります。

スライドバーがついていて、スライドさせると画像が入れ替わります。
また時間とともに画像が入れ替わります。

スライドバーの変化で画像を入れ替えるとともに、
NSTimerを使って時間経過とともに画像を入れ替えます。

完成形

f:id:kazuhei0108:20150126010315p:plain

f:id:kazuhei0108:20150126010328p:plain

コード

ひとまず全コードを貼っておきます。

import UIKit

class ViewController: UIViewController {
    
    let photos = ["写真1.png", "写真2.png", "写真3.png", "写真4.png", "写真5.png", "写真6.png"]
    
    var barHeight: CGFloat = 0.0
    var displayWidth: CGFloat = 0.0
    var displayHeight: CGFloat = 0.0
    var imageView: UIImageView!
    var timer: NSTimer!
    var slider: UISlider!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // statusbarの高さ取得
        barHeight = UIApplication.sharedApplication().statusBarFrame.size.height
        
        // viewのサイズ取得
        displayWidth = self.view.frame.width
        displayHeight = self.view.frame.height
        
        // 一枚目の原稿を読み込む
        let image:UIImage! = UIImage(named: photos[0])
        // 画像のアスペクト比を出して横幅いっぱいに画像を表示する
        let aspect:CGFloat = image.size.height / image.size.width
        imageView = UIImageView(frame: CGRect(x: 0, y: barHeight + ((displayHeight - barHeight - displayWidth * aspect) / 2), width: displayWidth, height: displayWidth * aspect))
        imageView.image = image;
        self.view.addSubview(imageView)
        
        // Sliderを作成する.
        slider = UISlider(frame: CGRectMake(0, 0, displayWidth*0.9, 20))
        slider.layer.position = CGPointMake(displayWidth / 2, displayHeight - 30)
        slider.backgroundColor = UIColor.whiteColor()
        slider.layer.cornerRadius = 10.0
        slider.layer.masksToBounds = false
        
        // 目盛の最小値と最大値を設定する.
        slider.minimumValue = 0
        slider.maximumValue = Float(manuscripts.count) - 1.0
        
        // Sliderの初期位置を設定する.
        slider.value = 0
        
        // Sliderの現在位置より右のTintカラーを変える.
        slider.maximumTrackTintColor = UIColor.grayColor()
        
        // Sliderの現在位置より左のTintカラーを変える.
        slider.minimumTrackTintColor = UIColor.greenColor()
        
        // 値が変化した時
        slider.addTarget(self, action: "onChangeValueSlider:", forControlEvents: UIControlEvents.ValueChanged)
        // 指を付けた時
        slider.addTarget(self, action: "timerStart:", forControlEvents: UIControlEvents.TouchUpInside)
        // 指を離した時
        slider.addTarget(self, action: "timerStop:", forControlEvents: UIControlEvents.TouchDown)
        
        self.view.addSubview(slider)
        
        timerInitialized()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func timerInitialized () {
        timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("nextPage"), userInfo: nil, repeats: true)
    }
    
    func timerStart (sender : UISlider) {
        if (!timer.valid) {
            timerInitialized()
        }
    }
    
    func timerStop (sender : UISlider){
        if (timer.valid) {
            timer.invalidate()
        }
    }
    
    // Sliderの値が変わった時に呼ばれるメソッド
    func onChangeValueSlider(sender : UISlider){
        let index = Int(sender.value)
        
        // 再びUIView処理済み画像を設定する.
        imageView.image = UIImage(named: photos[index])
        
        // 再描画をおこなう.
        imageView.setNeedsDisplay()
    }
    
    func nextPage (){
        slider.value++
        onChangeValueMySlider(slider)
    }
}

解説

ざっくりと解説。
Single View Applicationで作っていきます。
StoryBordやAutoLayout等のGUIは使いません。

変数の宣言

    let photos = ["写真1.png", "写真2.png", "写真3.png", "写真4.png", "写真5.png", "写真6.png"]
    
    var barHeight: CGFloat = 0.0
    var displayWidth: CGFloat = 0.0
    var displayHeight: CGFloat = 0.0
    var imageView: UIImageView!
    var timer: NSTimer!
    var slider: UISlider!


一枚目の画像を貼り付けます。
この際、横幅が画面いっぱいになるようにアスペクト比を使って計算しています。

        // statusbarの高さ取得
        barHeight = UIApplication.sharedApplication().statusBarFrame.size.height
        
        // viewのサイズ取得
        displayWidth = self.view.frame.width
        displayHeight = self.view.frame.height
        
        // 一枚目の原稿を読み込む
        let image:UIImage! = UIImage(named: photos[0])
        // 画像のアスペクト比を出して横幅いっぱいに画像を表示する
        let aspect:CGFloat = image.size.height / image.size.width
        imageView = UIImageView(frame: CGRect(x: 0, y: barHeight + ((displayHeight - barHeight - displayWidth * aspect) / 2), width: displayWidth, height: displayWidth * aspect))
        imageView.image = image;
        self.view.addSubview(imageView)

画像をはりつける高さはステータスバーを抜いて、上下の余白が均等になるように計算しています。


次にスライダーを作って、スタイルや初期値を設定します。

        // Sliderを作成する.
        slider = UISlider(frame: CGRectMake(0, 0, displayWidth*0.9, 20))
        slider.layer.position = CGPointMake(displayWidth / 2, displayHeight - 30)
        slider.backgroundColor = UIColor.whiteColor()
        slider.layer.cornerRadius = 10.0
        slider.layer.masksToBounds = false
        
        // 目盛の最小値と最大値を設定する.
        slider.minimumValue = 0
        slider.maximumValue = Float(manuscripts.count) - 1.0
        
        // Sliderの初期位置を設定する.
        slider.value = 0
        
        // Sliderの現在位置より右のTintカラーを変える.
        slider.maximumTrackTintColor = UIColor.grayColor()
        
        // Sliderの現在位置より左のTintカラーを変える.
        slider.minimumTrackTintColor = UIColor.greenColor()


スライダーにイベントを登録します。

        // 値が変化した時
        slider.addTarget(self, action: "onChangeValueSlider:", forControlEvents: UIControlEvents.ValueChanged)
        // 指を付けた時
        slider.addTarget(self, action: "timerStart:", forControlEvents: UIControlEvents.TouchUpInside)
        // 指を離した時
        slider.addTarget(self, action: "timerStop:", forControlEvents: UIControlEvents.TouchDown)

スライダーの値が変化したら、onChangeValueMySliderを呼んで画像を切替えます。

スライダーを操作している時にタイマーが動くと画像が切り替わってしまうので、
UIControlEvents.TouchUpInsideのイベントでtimerを止めて、
UIControlEvents.TouchDownのイベントで再びtimerを動かすようにしています。


タイマーは、2秒に1回nextPageの関数を呼ぶようにして、これで画像を次に進めています。

func timerInitialized () {
        timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("nextPage"), userInfo: nil, repeats: true)
    }

2秒ごと繰り返すのでrepeats: true
userInfoはselectorに渡す引数を指定しますが、今回は無いのでnilを渡しています。

メモ

addTargetのactionはtargetを追加される側のオブジェクトを引数を取らないとダメっぽい?(ちょっと良くわかってない)

func timerStart (sender : UISlider) {
    if (!timer.valid) {
        timerInitialized()
    }
}

↑の(sender: UISlider)の部分のことです。

蒲田の吉祥寺どんぶりが潰れて悲しい

この記事は「すた丼 Advent Calendar 2014」の17日目の投稿です。

すた丼 Advent Calendar 2014 - Adventar


大学時代に2年ほど蒲田駅付近でバイトをしていて、

その頃に通っていた店が、

「らーめん大」と「吉祥寺どんぶり」だった。

らーめん大についても書きたいことはたくさんあるが、
この記事はすた丼 Advent Calenderの記事なので、
今回は吉祥寺どんぶりについて書く。

蒲田の吉祥寺どんぶり

蒲田の吉祥寺どんぶりは、今はもうないので、
ここから書くことは完全に思い出話。

自分はきち丼と呼んで親しんでいた。

今からお店に行って確認することもできない。
悲しい。


蒲田のきち丼はいろいろ特徴的だった。

外観

まず外観、せっかくの透明な窓出できた入り口なのに、尋常じゃない張り紙の量だ。
最初入るときはかなり勇気が必要だった気がする。
f:id:kazuhei0108:20141217003332j:plain
f:id:kazuhei0108:20091007212714j:plain

内観

そして内観。なぜかやたらとオタクコンテンツが大量にあった。
ココらへんはなんとなく秋葉原のマンモスカレーを思い出させる。

f:id:kazuhei0108:20141217004004j:plain

f:id:kazuhei0108:20141217004128j:plain

f:id:kazuhei0108:20141217010032j:plain

そういえばマンモスカレーも閉店したんだったな。悲しい。

それから店中に、キャラクターの名言っぽいものが貼られていた。

f:id:kazuhei0108:20110719171015j:plain
f:id:kazuhei0108:20120202174905j:plain
f:id:kazuhei0108:20120220175420j:plain
f:id:kazuhei0108:20120213182516j:plain

BGM

そして、極めつけにBGMもちょっと前のアニソンが流れていた。

どのくらい前かというと、お店に通ってたのは2012年くらいのはずだが、
普通にハレ晴レユカイとか流れてたような気がする。

メニュー

メニューは自分みたいなバイト終わり(22:00)に白飯を大量にもさもさ食いたい人間には素晴らしい物だった。

主にタルタルから揚げ丼か、塩にんにく丼を食べていた気がする。
f:id:kazuhei0108:20141217004813j:plain
f:id:kazuhei0108:20110721140036j:plain

豚丼には生卵とお味噌汁がついていて、
テイクアウトしても生卵がついてきた。(割る前のやつがそのまま)

丼の量は多く、後述する学生カードとの併用でごはんだけで450gはあった。
具の部分も入れるとかなりの量だったはず。

そして、何よりも値段がやすかった。

学生カード

学生カードという見せると特典が受けられるカードがあった。

店のすぐ近くに日本工学院専門学校があったので、そこの学生達が使っていたのだと思う。

特典内容は

  • 肉増し無料
  • ご飯大盛り無料
  • 唐揚げ1個無料
  • スマイル無料

のいずれかだった。

スマイル無料を頼んでいるのは見たことがない。
自分はいつもこいつでご飯を450gに増量していた。


そして、このカード。
なんといっても特徴的なのはその見た目。

少しググったが、画像がなく。
かろうじて店に貼ってあったポスターを発見できた。
f:id:kazuhei0108:20141217005151j:plain

かなり見づらいが、学生カードにはソフトバンクのお父さん犬が印刷されていて、
頭の上に丼が載っけられていた。

この店は今や時価総額が日本で3位になるソフトバンクに単独で喧嘩を売っていた。

外観、内観、BGM、学生カードの部分は吉祥寺のきちどんに行った時にはなかったので、
蒲田店特有だったんだと思う。

吉祥寺どんぶりが潰れた話

蒲田のきち丼が潰れた正確な理由は自分程度の一般客にはわからないのだが、

きち丼のすぐ斜め向かい側にすた丼ができたことが大きな理由の一つであることは間違いない。

きちどんはそもそもすた丼のインスパイアなので、
本家が目の前にできてしまってはどうしようもなかったのかもしれない。

すた丼ができてから、きち丼はから揚げ丼専門店に姿を変えることになる。

自分はきち丼派ですた丼を横目にきち丼に通っていた。

しかし、その努力もむなしく、潰れてしまった。

とても残念だった。

あとがき

今回この記事を書くにあたって、
たくさんググってとても懐かしい気持ちになれました。

お店が潰れてしまった以上、もう行くことはできないので、
他ブログ様や食べログから画像を拝借してきました。

拝借元

写真の転載に問題がありましたらご連絡ください。

私の所持金52円!!

この記事は「ごちうさ住民 Advent Calendar 2014」の8日目の投稿です。

ごちうさ住民 Advent Calendar 2014 - Adventar


f:id:kazuhei0108:20141207213941j:plain

こころぴょんぴょん.comの発表にごちうさ住民が歓喜する中、
早くも次の日です。


アドベントカレンダーといえば、もうクリスマスが間近に迫っていますね。

今日が8日目なので、クリスマスまであと17日ですか。

クリスマスというとごちうさ11話を思い出しますね。

大福にローソクを立てて一人でクリスマスを過ごすシャロ...

f:id:kazuhei0108:20141207214557j:plain



クリスマスにビラ配りをするマッチ売りの少女シャロ
f:id:kazuhei0108:20141207224333j:plain


こんな貧乏で頑張り屋さんなシャロを養いたい!!!



…ということでシャロにお金をあげてみました。

f:id:kazuhei0108:20141207215045p:plain

ここに10万円あるからなんでも欲しい物を買いなさい。フフフ...




僕はごちうさのキャラではシャロが好きで、最初の方のバレーボールのところで出てくる私服が好きですね。
f:id:kazuhei0108:20141207215555j:plain

中の人が内田真礼さんなのもいいですね。



以下ごちうさと関係ない話

簡単に雑コラ画像が作れるアプリケーションを作りました

雑コラメーカー


シャロにお金をあげる画像はこれを使って作りました。

メインになる画像をアップロードして
f:id:kazuhei0108:20141207215959p:plain


位置を調節
f:id:kazuhei0108:20141207220027p:plain


素材にする画像をアップロードして素材として使う範囲を確定
f:id:kazuhei0108:20141207220101p:plain


切り出した素材をメイン画像にドラッグ&ドロップで貼り付けできます。
f:id:kazuhei0108:20141207220150p:plain


素材に透過pngを使うといい感じにコラ画像が作れます。


おっとちょうど良い所にティッピーの透過pngが...

f:id:kazuhei0108:20141207222338p:plain


使ってみました。


f:id:kazuhei0108:20141207222446p:plain




ということで良かったら遊んでみてくださいな。
(現状chromeでしか正しく動きません…)

f:id:kazuhei0108:20141207233338j:plain

制作について

このアプリケーションは
かずへい (@kazuhei0108) | Twitter
たまごもり (@sugaret) | Twitter
Tweets with replies by ともや (@itatomo124) | Twitter
の3人で作りました。

UICollectionViewを実装する[swift&xcode6]

今回やること

swift&xcode6という最新っぽい環境でUICollectionViewを実装します。

ゴールはこんな感じです。
f:id:kazuhei0108:20141126001216p:plain
f:id:kazuhei0108:20141126001229p:plain

タイル状に画像を並べ、並んだ画像をタップすると画像の詳細画面に移動するというものです。
また移動後は画面上部のBackから元の一覧に戻ることが出来ます。

作り方は、こちらの動画を参考にさせていただきました。

UICollectionView. Swift, Xcode 6 - YouTube

手順

ストーリーボードで

  • Navigation Controller で 画面上部の < Backを作成
  • UICollectionView でタイル表示を作成
  • 詳細画面の作成
  • タイルと詳細画面をリンク

プログラム側で

  • UICollectionViewとControllerのヒモ付
  • タイル表示のタイルとUICollectionViewCellのヒモ付
  • 詳細画面とControllerのヒモ付

実装

最初はやること全部書こうかと思ったんですが、
上の動画が素晴らしすぎる&ストーリーボードの説明しんどいので、
コードだけ載せて置きます。

基本的に動画を見ていただければということで。


一枚目のタイル表示のViewController

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    var arr:[String] = []
    var name: AnyObject? {
        get {
            return NSUserDefaults.standardUserDefaults().objectForKey("name")
        }
        set {
            NSUserDefaults.standardUserDefaults().setObject(newValue!, forKey: "name")
            NSUserDefaults.standardUserDefaults().synchronize()
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        for (var i = 1; i <= 30; i++) {
            arr.append(String(i) + ".jpeg")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return arr.count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as myViewCell
        cell.imgView.image = UIImage(named: arr[indexPath.row])
        return cell
    }
    
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        name = arr[indexPath.row]
    }
}

UICollectionViewを使うために、UICollectionViewDataSource, UICollectionViewDelegateを継承しています。

今回のサンプルでは値の受け渡しにuserDefaultを利用しています。
処理の内容としては、arrに画像のパスを格納、


CollectionViewのCellそれぞれに画像を詰めていきます。

そしてタイルが選択された場合は、nameというキーでuserDefaultに選ばれた画像パスを渡しています。


2枚目の詳細画面のViewController

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var newImgView: UIImageView!
    var name: AnyObject? {
        get {
            return NSUserDefaults.standardUserDefaults().objectForKey("name")
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        newImgView.image = UIImage(named: name as String)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

}

一枚目のViewControllerでuserDefaultに保存した画像パスを取り出して、UIImageを作って表示しています。

最後にCollectionViewのセル(タイル)のコード

import UIKit

class myViewCell: UICollectionViewCell {
    
    @IBOutlet weak var imgView: UIImageView!
}

InterfaceBuilderで貼り付けただけですが。

Vagrant+gruntでCSSが更新されない

Vagrant環境で開発している時に、

git pullでファイルを持ってきたり、
gruntでcssを自動生成したりした時に、
cssが更新されないということがありました。

共有ファイルのPC側のファイルは正しく変更されているのに、
実際のページには反映されていないという状況でした。

結論からいうと、これはvagrant上のapacheの設定の問題で、

httpd.confに

EnableSendfile Off

と書くと、問題なく機能するようになりました。

herokuのmemcachierをfuelPHPで使えるようにする

はじめに

heorkuでは、様々な機能がaddonという形で提供されています。
MySQLサーバーのaddonであるClearDBや、
MemcacheサーバーのaddonであるMemcachierなどがそうです。

これらは利用しているストレージのプランでお金がかかります。
といっても開発用の小規模なものは無料で借りられます。

今回はfuelPHPのcoreのcache部分のライブラリを拡張して、
memcachierを使えるようにします。

前提

herokuのtoolbeltやssh設定などは一通り終わってるものとします。

本編

herokuにmemcachierを入れる

アプリケーションが複数ある場合にはheorkuコマンドには --app [appID]を必ず付けてください。

$ heroku addons:add memcachier:dev

これで開発用のmemcachierサーバーを確保することができます。

$ heroku config
...
MEMCACHIER_SERVERS    => mcX.ec2.memcachier.com
MEMCACHIER_USERNAME   => bobslob
MEMCACHIER_PASSWORD   => l0nGr4ndoMstr1Ngo5strang3CHaR4cteRS
...

これでmemcachierサーバーと接続するために必要な情報が得られます。

見たまんまですが、サーバー、ユーザー名、パスワードです。

memcacheを使うときとmemcachierを使うときの違い

memcachierを使うのはmemcacheを使うのとほとんど同じなのですが、
memcachierは自分のサーバー内に有るわけではなく、
外部でサービスとして提供されています。

この外部のサービスに接続するために、
coreのCache_Storage_Memcachedmemcachedインスタンスを生成する際に、
認証情報を付加する必要があります。

この認証情報の付加ができるCache_Storage_Memcachierクラスを自作してみます。


Cache_Storage_Memcachierクラスを作る

といってもcoreのCache_Storage_Memcachedを数行書き換えただけです。

fuelphp Cache_Storage_Memcachier to use memcachier on heroku




<?php

static::$memcached->setSaslAuthData( getenv("MEMCACHIER_USERNAME"), getenv("MEMCACHIER_PASSWORD") );

の部分で認証情報を付加しています。
getenv("MEMCACHIER_USERNAME")にさっきのユーザー名、
getenv("MEMCACHIER_PASSWORD")にさっきのパスワードが入ってきます。

Cache_Storage_Memcachierクラスをfuelに読み込ませる。

ところでこのファイルをどこに置くかという話ですが...
正直どこでも良いようです。

というのはbootstrap.phpに正しく記述すればどこでも動くからです。

このファイルを
app/classes/cache/memcachier.php
という位置に作ったとしたら、

app/bootstrap.php

Autoloader::add_classes(array(
    'Cache_Storage_Memcachier' => APPPATH.'classes/cache/memcachier.php'
));

というふうに書いてください。
これでCache_Storage_Memcachierクラスがapp/classes/cache/memcachier.phpにあることを
fuelphpに教えることができます。

設定ファイルを書く

<?php
return array(
    'driver' => 'memcachier',
    'memcachier'  => array(
        'cache_id'  => 'fuel', 
        'servers'   => array(   
            'default' => array('host' => 'mcX.ec2.memcachier.com', 'port' => 11211, 'weight' => 100)
        ),
    ),
);

という感じで設定します。
これでdriver部分をmemcachedからmemcachierに切り替えるだけで、
簡単に移行できます。

おわりに

以上で、fuelphpからherokuのmemcachierが使えるようになりました。

fuelPHPでアプリケーションを作る前に知っておきたかったこと

はじめに

fuelPHPを使っていたら、あとからあとから、
こんな便利機能あったのかよ!自分で実装しちゃったよ...。
というのが出てきたので、今からfuelPHPを書き始める人のためにここに書いておきます。

といっても全部リファレンスに書いてあることなので、
リファレンスちゃんと読めって話だったのですが。

namespaceや独自のライブラリはbootstrap.phpに記述すれば簡単に増やせる。

bootstrap.phpに登録しさえすれば、自分の思うとおりにパッケージを切り分けられます。
fuelはMVCを基本としていますが、これを使えばドメイン駆動設計したり、
modelにならないようなクラスを置くための場所を作ったりなど、
柔軟に開発できます。
FuelPHPを使って開発する時に最初にやったこと | tech.ewdev.info

論理削除ができる。

通常、モデルはModelクラスを継承して作りますが、
Model_Softを継承してモデルを作り、削除フラグを設定すると論理削除ができます。
リレーションを使った論理削除もできますし、簡単に復元もできます。
論理削除モデル - Orm パッケージ - FuelPHP ドキュメント

一括読み込みと遅延読み込みが選べる

ORMを使ってリレーションのあるレコードを取得するとき、
リレーションの子に当たるレコード情報を一括で取得するか、必要になってから取得するか選ぶことができます。
デフォルトは遅延取得になっています。
はじめに - Relations - Orm Package - FuelPHP ドキュメント

コアの拡張が簡単にできる

fuelPHPではコアを継承して全く同じクラス名でbootstrap.phpに設定を書くと、
あたかも元からそれがcoreのクラスだったかのように動いてくれます。
自分はそれを知らなかったので、herokuで自作coreを使うために、
githubのfuelを自分のリポジトリにfolkしてcomposerに読み込ませるという
アホなことをしました...とほほ。
Core の拡張 - 概要 - FuelPHP ドキュメント

結論

自分が欲しいものは、だいたいある!
リファレンス読もうね。

FuelPHP ドキュメント