とこなつとこなつ

swift について勉強したことの忘備録とか

AVFoundation を使ってカメラアプリを作成

撮った写真を加工してごにょごにょするアプリを作ったのでメモ。
撮った写真を画面上に表示させるだけの簡単コード。
変数名が適当ですがあくまでメモなので。。

storyboard には撮影開始ボタンと imageView を配置。
ボタンは cameraStart と接続。
imageView は captureImage と接続。

import UIKit
import AVFoundation

class ViewController: UIViewController {

    var session : AVCaptureSession!
    var device : AVCaptureDevice!
    var imageOutput : AVCaptureStillImageOutput!
    var videoLayer : AVCaptureVideoPreviewLayer!
    var button : UIButton!
    @IBOutlet weak var captureImage: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidDisappear(animated: Bool)
    {
        // カメラの停止とメモリ解放.
        self.session.stopRunning()
        for output in self.session.outputs
        {
            self.session.removeOutput(output as! AVCaptureOutput)
        }
        for input in self.session.inputs
        {
            self.session.removeInput(input as! AVCaptureInput)
        }
        self.session = nil
    }
    
    @IBAction func camerastart(sender: AnyObject) {
        // セッションの作成.
        session = AVCaptureSession()
        
        // デバイス一覧の取得.
        let devices = AVCaptureDevice.devices()
        
        // バックカメラをmyDeviceに格納.
        for device in devices{
            if(device.position == AVCaptureDevicePosition.Back){
                self.device = device as! AVCaptureDevice
            }
        }
        
        // バックカメラからVideoInputを取得.
        do {
            let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
            let videoInput = try AVCaptureDeviceInput(device: captureDevice)
            // セッションに追加.
            session.addInput(videoInput)
        } catch {
            
        }
        
        // 出力先を生成.
        imageOutput = AVCaptureStillImageOutput()
        
        // セッションに追加.
        session.addOutput(imageOutput)
        
        // 画像を表示するレイヤーを生成.
        videoLayer = AVCaptureVideoPreviewLayer(session: session)
        videoLayer.frame = self.view.bounds
        videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        self.view.layer.addSublayer(videoLayer)
        
        // セッション開始.
        session.startRunning()
        
        // UIボタンを作成.
        button = UIButton(frame: CGRectMake(0,0,120,50))
        button.backgroundColor = UIColor.redColor();
        button.layer.masksToBounds = true
        button.setTitle("capture", forState: .Normal)
        button.layer.cornerRadius = 20.0
        button.layer.position = CGPoint(x: self.view.bounds.width/2, y:self.view.bounds.height-50)
        button.addTarget(self, action: "capture:", forControlEvents: .TouchUpInside)
        
        self.view.addSubview(button);
    }
    
    func capture(sender: UIButton) {
        // ボタンを連続して押せないようにする
        self.button.enabled = false
        let videoConnection = imageOutput.connectionWithMediaType(AVMediaTypeVideo)
        self.videoLayer.removeFromSuperlayer()
        self.button.removeFromSuperview()
        
        // 接続から画像を取得.
        self.imageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: { (imageDataBuffer, error) -> Void in
            
            // 取得したImageのDataBufferをJpegに変換.
            let imageData : NSData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataBuffer)
            
            // JpegからUIIMageを作成.
            let image : UIImage = UIImage(data: imageData)!
            
            self.captureImage.image = image
            // カメラをストップ
            self.session.stopRunning()
        })
    }
}

カメラを起動して写真を撮るだけなのに結構なコード書かないとだめなんですね。。。

完成品

f:id:tokonatsutan:20160314211148j:plain

Rails 作業メモ1 遷移元のコントローラー取得

Rails のことも少し。

遷移元のコントローラを判別しないといけない機会があったのでその方法をメモ

遷移元のコントローラーを取得
Rails.application.routes.recognize_path(request.referrer)
 {:controller=>"users", :action=>"index"}

こちらで遷移元の URL が取得できる
request.referrer
 "http://localhost:3000/users"

勉強になりました。

swift で UICollectionView を使って曜日を表示したカレンダーを作成する

swift でカレンダーを作成しました。
↓こんな感じです。曜日も表示してます。ちょっと味気ないけどとりあえずできたので。
f:id:tokonatsutan:20150714203540p:plainf:id:tokonatsutan:20150714203550p:plain

storyboard はこんな感じ
f:id:tokonatsutan:20150714203731p:plain
UICollectionView を配置して、cell に自前のクラスを設定するくらい。
日付を表示するラベルを自前のクラスに持たせてるので。あとは delegate を ViewController に繋いでるくらい。
vie は CollectionView と繋いでます。多分 view って書いたつもりなんでしょうね。

コードはこんな感じ。解説とかはコード中に記載。あとで見て分かるかな…

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout  {

    let dayPerWeeks = 7
    let cellMargin: CGFloat = 2
    // 表示中の月のデータのコンテナ
    var currentDate: NSDate!
    let week: [String] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
    @IBOutlet weak var vie: UICollectionView! // vie ってなんだ
    
    override func viewDidLoad() {
        super.viewDidLoad()
        currentDate = NSDate()
        var date = NSDateFormatter()
        date.dateFormat = "yyyy/M"
        self.title = date.stringFromDate(currentDate)
    }
    
    // 先月押下時のアクション
    @IBAction func prevTap(sender: AnyObject) {
        var calendar = NSCalendar.currentCalendar()
        var dateComponents = NSDateComponents()
        // 1月マイナス
        dateComponents.month = -1
        var tmpDate = NSDate()
        self.currentDate = calendar.dateByAddingComponents(dateComponents, toDate: self.currentDate, options: NSCalendarOptions(0))!
        var formattar = NSDateFormatter()
        formattar.dateFormat = "yyyy/M"
        self.title = formattar.stringFromDate(currentDate)
        // view を更新する
        self.vie.reloadData()    
    }
    
    // 次月ボタン押下時のアクション
    @IBAction func nextTap(sender: AnyObject) {
        var calendar = NSCalendar.currentCalendar()
        var dateComponents = NSDateComponents()
        // 1月プラス
        dateComponents.month = 1
        var tmpDate = NSDate()
        self.currentDate = calendar.dateByAddingComponents(dateComponents, toDate: self.currentDate, options: NSCalendarOptions(0))!
        var formattar = NSDateFormatter()
        formattar.dateFormat = "yyyy/M"
        self.title = formattar.stringFromDate(currentDate)
        // view を更新する
        self.vie.reloadData()   
    }
    
    // その月の初日を返す
    func firstDateOfMonth() -> NSDate {
        var calendar = NSCalendar.currentCalendar()
        var component =  calendar.components(NSCalendarUnit.CalendarUnitYear|NSCalendarUnit.CalendarUnitMonth|NSCalendarUnit.CalendarUnitDay|NSCalendarUnit.CalendarUnitDay,fromDate:currentDate)
        component.day = 1
        var firstDateOfMonth = calendar.dateFromComponents(component)
        
        return firstDateOfMonth!
    }
    
    // index -> 日付へ変換する
    func dateForCellAtIndexPath(indexPass:NSIndexPath) -> NSDate {
        // 月の初日が何番目かを調べる
        var calendar: NSCalendar = NSCalendar.currentCalendar()
        var firstDay = calendar.ordinalityOfUnit(NSCalendarUnit.CalendarUnitDay,
            inUnit: NSCalendarUnit.CalendarUnitWeekOfMonth,
            forDate: firstDateOfMonth())
        
        // 「月の初日」と「indexPath.item番目のセルに表示する日」の差を計算する
        var dateComponents = NSDateComponents()
        
        // +7 は曜日表示分ずらすため
        dateComponents.day = indexPass.item - (firstDay - 1 + 7)
        var date = calendar.dateByAddingComponents(dateComponents, toDate: firstDateOfMonth(), options: NSCalendarOptions(0))!
        
        return date
    }
    
    // Mark: UICollectionViewDataSource methods
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        var calendar = NSCalendar.currentCalendar()
        
        // その月が何週あるか調べる
        var rangeOfWeeks = calendar.rangeOfUnit(NSCalendarUnit.CalendarUnitWeekOfMonth,
                                                inUnit: NSCalendarUnit.CalendarUnitMonth,
                                                forDate: firstDateOfMonth())
        // 1ヶ月が何週あるか
        var numberOfWeeks = rangeOfWeeks.length
        // その月のカレンダーの総日数。
        // +1 しているのは曜日の列分追加するため
        var numberOfItems = (numberOfWeeks + 1) * dayPerWeeks
        
        return numberOfItems
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        // UICollectionViewCell. カレンダーの日付ラベルを持ってるくらい。
        var cell: customCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! customCell
        // 1列目( < 7)は曜日をセットする
        if indexPath.row < 7 {
            cell.label!.text = week[indexPath.row]
        } else {
            // 2列目からはカレンダーに表示する日付
            var formatter = NSDateFormatter()
            formatter.dateFormat = "d"
            cell.label!.text = formatter.stringFromDate(self.dateForCellAtIndexPath(indexPath))
        }
        return cell
    }
    
    // Mark: UICollectionViewDelegateFlowLayout methods
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        
        let numberOfMargin = CGFloat(8)
        let rateOfWidth = CGFloat(1.5)
        
        var width: CGFloat = floor((collectionView.frame.size.width - CGFloat(cellMargin) * numberOfMargin) / CGFloat(dayPerWeeks))
        
        // width と height は 1:1.5
        var height: CGFloat = width * rateOfWidth
        
        return CGSizeMake(width, height)
    }
    
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        return UIEdgeInsetsMake(cellMargin, cellMargin, cellMargin, cellMargin)
    }
    
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
        return cellMargin
    }
    
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
        return cellMargin
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }   
}

コードはなんとか動くものが作れるけど AutoLayout が難しい。
StoryBoard に表示してあるものをそのまま画面サイズに合わせて表示してくれればいいのに…
いずれその辺なんとかしたいな。

UITextField (UITextView) を IBOutlet 接続せずにキーボードで隠れないようにする。

テキストフィールドやテキストビューがキーボードに隠れてしまうので、それにあわせて scrollView を移動させるやり方を勉強したのでメモ。
サンプルとかはググったら色々出てくるんだけど、テキストフィールドを IBOutlet で接続してやる方法が多かった。
それだと、view にテキストフィールドがたくさん乗ってるときは使いにくいなと思いました。

@IBOutlet weak var textField1: UITextField!

…

@IBOutlet weak var textField5: UITextField! 

できれば↑はしたくないということで、作ってみたコードをぺたり。

class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {

    @IBOutlet weak var scrollView: UIScrollView!
    var frameOrigin:CGRect = CGRect(x: 0, y: 0, width: 0.0, height: 0.0)

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func viewWillAppear(animated: Bool) {
        // notification 登録
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);
    }

    // 画面がタップされたときに呼ばれる
    @IBAction func tapped(sender: AnyObject) {
        // キーボードを非表示にする
        self.view.endEditing(true)
    }

    func textFieldShouldReturn(textField: UITextField) -> Bool {
        // フォーカスを外す(キーボードを非表示にする)
        textField.resignFirstResponder()
        return true
    }

    func textFieldDidBeginEditing(textField: UITextField) {
        // キーボードの出たときの移動の判定用にとっておく
        self.frameOrigin = CGRect(x: textField.frame.origin.x, y: textField.frame.origin.y, width: textField.frame.size.width, height:textField.frame.size.height)
    }

    // keyboardWillShow より先に呼ばれる(did ではダメ)
    func textViewShouldBeginEditing(textView: UITextView) -> Bool {
        // キーボードが出た際に scrollview の移動判定用にとっておく
        self.frameOrigin = CGRect(x: textView.frame.origin.x, y: textView.frame.origin.y, width: textView.frame.size.width,  height: textView.frame.size.height)
        return true
    }

    func keyboardWillShow(notification: NSNotification?) {
        // キーボードの情報を取得
        let rect = (notification?.userInfo?[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        // view の底からの高さ
        let textHeight = self.view.frame.height - self.frameOrigin.origin.y - self.frameOrigin.size.height
        let keyboardHeignt = rect.size.height

        // キーボードで隠れてしまうか
        if (keyboardHeignt > textHeight)
        {
            // キーボードで隠れるので、scrollView を移動させる
            let margin = CGFloat(5.0)
            let point = CGPoint(x: 0, y: keyboardHeignt - textHeight + margin)
            // テキストフィールド(ビュー)がキーボードの上に来るように移動
            self.scrollView.setContentOffset(point, animated: false)
        }
    }
    func keyboardWillHide(notification: NSNotification?) {
        // キーボードが非表示になるので、scrollView を元に戻す
        self.scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false)
    }
}

UITextField と UITextView は StoryBoard 上で ViewController の Delegate と接続しています。
しかし、読みにくすぎますね。
f:id:tokonatsutan:20150705201111p:plainf:id:tokonatsutan:20150705201122p:plain
キーボードにテキストビューが隠れてませんね。とりあえずできた。
もっといい方法があれば教えていただければ幸いです。

swift と objective-c では setter の書き方が違うらしい

swift でセッターを用意したときのこと。
以下のようなコードを書いたんだけど

var str: NSString!

func setStr(inputStr:NSString) {
    self.str = inputStr   
}

するとこんなエラーが。
Method 'setStr' with Objective-C selector 'setStr:' conflicts with setter for 'str' with the same Objective-C selector

調べてみると swift の setter の書き方はどうも違うらしい。
以下のように書き換えてみた。

var str: NSString!{
    set(inputStr){
            
    }
}

またこんなエラーが
Variable with a setter must also have a getter
setter 使うなら getter も設定してねとのことっぽい。言われるままに getter も追加した。
またまた以下に修正した

var str: NSString!{
    set(inputStr){
        _str = inputStr
    }
    get {
        return _str
    }
}

これも怒られる。
Use of unresolved identifier '_str'
swift は内部的に _xxxx といったのを作ってくれないっぽいですね。
結局以下のようなコード書いたんだけど、こんなことしないといけないんですかね?

var tmpStr: NSString = ""
var str: NSString!{
    set(inputStr){
        tmpStr = inputStr
    }
    get {
        return tmpStr
    }
}

だったら set とか get はもうお任せします…
もっといい方法あればご教授いただければと思います。