Yumico’s blog

iOS開発ブログ

swift3.0: Arrayの中身をソートする。

Arrayの中身が独自クラスの場合のソート方法

class Item: NSObject {
    var id: String = ""
    var itemName: String = ""
}

class data: NSObject {
    func prepareItem() {
        ///ソートするためのデータを用意する
        var list: [Item] = []
        let item1: Item = Item()
        item1.id = "id_a"
        item1.itemName = "aaa"
        list.append(item1)
        let item2: Item = Item()
        item2.id = "id_b"
        item2.itemName = "bbb"
        list.append(item2)
        let item3: Item = Item()
        item3.id = "id_c"
        item3.itemName = "ccc"
        list.append(item3)
        
        ///ソート用のデータlistをソートする(昇順)
        let ascending : [Item] = list.sorted(by: {$0.itemName < $1.itemName})
        ///ソート用のデータlistをソートする(降順)
        let descending: [Item] = list.sorted(by: {$0.itemName > $1.itemName})
    }
}

UIViewを曲線上にアニメーションさせる方法

今回は半径分だけアニメーションさせて見ます。 f:id:Yumico:20170116191902g:plain

まずは全コードを。。
開発環境:Xcode8 + swift3
スタートボタンのイベントはIBかストーリーボードで設定して繋げてください。

import UIKit

class CurveAnimationViewController: UIViewController {

    let animateView: UIView = UIView()
    let viewSize: CGFloat = 100.0
    let curcleRadius: CGFloat = 150.0
    let duration: TimeInterval = 0.5
    var isDownView: Bool = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        animateView.frame = CGRect(x: (UIScreen.main.bounds.width / 2) - (viewSize / 2), y: UIScreen.main.bounds.height / 2 - viewSize / 2, width: viewSize, height: viewSize)
        animateView.layer.cornerRadius = viewSize / 2.0
        animateView.backgroundColor = UIColor.orange
        self.view.addSubview(animateView)      
    }
    
    @IBAction func tappedStartButton(_ sender: UIButton) {
        isDownView = !isDownView
        if isDownView {
            self.downAnimation()
        }else{
            self.upAnimation()
        }
    }
}


//MARK: - animation
extension CurveAnimationViewController {
    
    func downAnimation() {
        animateView.layer.removeAnimation(forKey: "end")
        let animation: CAKeyframeAnimation = self.animation()
        let path: CGMutablePath = CGMutablePath()      
        let startPoint: CGPoint = CGPoint(x: animateView.frame.minX + (animateView.frame.width / 2.0), y: animateView.frame.minY + (animateView.frame.height / 2.0))
        let midPoint: CGPoint = CGPoint(x: startPoint.x + curcleRadius, y: startPoint.y + curcleRadius / 2.0)
        let endPoint: CGPoint = CGPoint(x: startPoint.x, y: startPoint.y + curcleRadius)
        
        path.move(to: startPoint)
        path.addQuadCurve(to: endPoint, control: midPoint)
        animation.path = path
        animateView.layer.add(animation, forKey: "start")
    }
    
    func upAnimation() {
        animateView.layer.removeAnimation(forKey: "start")
        let animation: CAKeyframeAnimation = self.animation()
        let path: CGMutablePath = CGMutablePath()
        let startPoint: CGPoint = CGPoint(x: animateView.frame.minX + (animateView.frame.width / 2.0), y: animateView.frame.minY + (animateView.frame.height / 2.0) + curcleRadius)
        let midPoint: CGPoint = CGPoint(x: startPoint.x + curcleRadius, y: startPoint.y - curcleRadius / 2.0)
        let endPoint: CGPoint = CGPoint(x: animateView.frame.minX + (animateView.frame.width / 2.0), y: startPoint.y - curcleRadius)
        
        path.move(to: startPoint)
        path.addQuadCurve(to: endPoint, control: midPoint)
        animation.path = path
        animateView.layer.add(animation, forKey: "end")
    }

    func animation() -> CAKeyframeAnimation {
        let animation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "position")
        animation.fillMode = kCAFillModeForwards
        animation.isRemovedOnCompletion = false
        animation.duration = duration
        return animation
    }
}

詳しく説明をしていきます。
今回はUIViewをアニメーションさせるためにCAKeyframeAnimationを使用しています。
func animation() -> CAKeyframeAnimation内で設定している内容は下記の通りです。

CAKeyframeAnimation 説明
fillMode アニメーションの再生中もしくは再生後のスタイル。 プロパティについてはこちらのサイトがわかりやすくまとめていました。【iOS Swift アニメーション入門 #4】Core AnimationのfillModeプロパティ | Swift,Objective-Cプログラミング ~ iOS ~
isRemovedOnCompletion アニメーション完了時に、ターゲットのレイヤーからアニメーションを削除するかどうかを設定。デフォルトはYes
duration アニメーションの長さ。

このCAKeyframeAnimationに作成したパスを入れて、動かしたいUIViewのレイヤーに加えてあげればアニメーションが開始されます。
では、パスをどのように設定しているか見ていきしょう。

let path: CGMutablePath = CGMutablePath()
//出発地点 > 動かしたいViewのセンターを計算しています。
let startPoint: CGPoint = CGPoint(x: animateView.frame.minX + (animateView.frame.width / 2.0), y: animateView.frame.minY + (animateView.frame.height / 2.0) + curcleRadius)
//中間地点 > 出発地点と最終地点の真ん中の位置を計算します。
let midPoint: CGPoint = CGPoint(x: startPoint.x + curcleRadius, y: startPoint.y - curcleRadius / 2.0)
//最終地点 > 最終地点のポジション。
let endPoint: CGPoint = CGPoint(x: animateView.frame.minX + (animateView.frame.width / 2.0), y: startPoint.y - curcleRadius)
        
//出発地点のポイントを設定します。
path.move(to: startPoint)
//最終地点と中間地点を設定します。
path.addQuadCurve(to: endPoint, control: midPoint)
//先ほどのCAKeyframeAnimationにパスを設定。
animation.path = path
//UIViewにCAKeyframeAnimationを追加。
animateView.layer.add(animation, forKey: "start")

pathの数値を変えるなどして試して見てください。
特に難しい計算を必要とせずに、どこに向かってアニメーションさせたいかのポイントを入れるだけなので、とても簡単に実装できます。

CocoaPods:ライブラリを追加する

すでにあるPodfileに、他のライブラリをさらに追加してインストールしたい。
そんな時も、初めてPodfileに書いたライブラリをインストールする時も、インストールする時はどんな時も $ pod install を使おう!
間違っても $ pod update もしくは $ pod update [PODNAME] は使わない。
なぜか?それは、update と install の違いを見ると明らかです。
※わりと $ pod update を使えと書いてある記事が多いので注意してください。

$ pod install

Podfileに書かれたライブラリをプロジェクトにインストールします。
Podfileに新しく追記されたライブラリだけをインストールするので、すでにインストール済みのライブラリは何も変化しません。
ただしPodfileから削除したライブラリはアンインストールされます。

$ pod update

すでにインストールされているライブラリのバージョンをアップデートします。
インストール済みのライブラリとPodfileに新しく記述したバージョン(バージョン指定していない時は、最新のバージョン)が異なっている場合、アップデートを開始します。
pod nameを指定していないと全てのライブラリが更新されてしまいますので注意が必要です。

ちなみに・・・

Podfileが置かれているディレクトリにPodfile.lockというファイルがあります。
このPodfile.lockというファイルにはインストールしたライブラリのバージョン情報が書かれおり、$ pod update した時にはこのファイルを見てアップデートをする必要があるかどうかを判別しています。

詳しくは本家サイトにも書かれていますので、参考にしてください。
guides.cocoapods.org

初めてCocoaPodsを導入する方は、下記の記事がとても分かりやすかったのでオススメです。
qiita.com

UINavigationControllerを使わない時のナビゲーションバーがステータスバーに被らない件。

タイトルがわかりにくくてごめんなさい。
つまり、こういうことデス。
f:id:Yumico:20161202172439p:plain:w500

はい、とてもかっこ悪いので修正していきます。
いつも通り完コピ用ソースです。
開発環境:Xcode8 + swift3

import UIKit

class ViewController: UIViewController,UINavigationBarDelegate {
    @IBOutlet weak var navigationBar: UINavigationBar!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationBar.delegate = self
    }

    func position(for bar: UIBarPositioning) -> UIBarPosition {
        return .topAttached
    }
}

やっていることは簡単で、UINavigationBarDelegateのfunc position(for bar: UIBarPositioning) -> UIBarPositionを実装するだけ。 戻り値は.topAttached
navigationBar.delegate = selfを忘れずに。。

f:id:Yumico:20161202172641p:plain:w500

swift: テキストの高さを取得する簡単な方法(UITextView,UILabel)

なんだかんだでよく使う、表示されるテキストの高さを取得する方法。
開発環境:Xcode8 + swift3

UILabel

    func labelHeight(label: UILabel) -> CGFloat{
        label.sizeToFit()
        return label.frame.height
    }

UITextView

    func textViewHeight(textView: UITextView) -> CGFloat {
        return textView.contentSize.height
    }

ただ、上記の方法だとUIから直接取得しているのでAutoLayoutのタイミングとか、Viewを読み込む処理スピードの低下とか色々都合が悪い時がある。 その場合は、NSStringクラスを使用して高さを取得しよう!

    func labelHeight(label: UILabel, s: String) -> CGFloat {
        let str: NSString = NSString(string: s)
        let size : CGSize = CGSize(width: label.frame.width, height: CGFloat.greatestFiniteMagnitude)
        let att: [String: Any] = [NSFontAttributeName: label.font]

        let rect: CGRect = str.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: att, context: nil)
        return rect.height
    }

高さを取得しているのはNSStringクラスのstr.boundingRect(with: CGSize, options: NSStringDrawingOptions, attributes: [String : Any]?, context: NSStringDrawingContext?)メソッド。 設定する引数は下記の通り。

引数 説明
CGSize 表示するUIのサイズ。高さを取得したい場合は幅を表示サイズに、高さをCGFloat.greatestFiniteMagnitudeに設定。 幅を取得したい場合はその逆。
NSStringDrawingOptions テキストの表示方法。 2007-06-19 - at_yasuの日記もといメモ書きにわかりやすく記載してある。
[String : Any]? Attributesを設定。特にAttributedStringとかを使用していない場合は、[NSFontAttributeName: label.font]で良いです。

UITextViewの場合はUILabelをUITextViewに書き換えて使用してください。

UITableViewを実装してみよう!

f:id:Yumico:20161127144055p:plain:w230:right iOSエンジニアとして最初の登竜門的なUIがUITableViewだと思う。
これが理解できたらある程度どんなアプリも開発できる気がする。
まずは基本的な実装方法を。。。
Xcodeでプロジェクトを作成しておいてください。

開発環境:Xcode8 + swift3

1.UITableViewCellを準備しよう

UITalbeViewCellとはテーブルビュー内で表示されるリストの内容です。
基本的に同じデザインを繰り返し表示させる仕組みのため、スクリーンから消えたセルは次に表示されるセルに使い回されます。
エコですね!

UITalbeViewCellのファイルを追加します。

Xcode上で⌘+n>Cocoa Touch Classを選択してNextボタンをタップすると下記の様な感じになります。
f:id:Yumico:20161127151846p:plain

  • Class: 任意のクラス名を入力(今回はTableViewCellとします)
  • Subclass of: UITableViewCellと入力
  • Also create XIB file のチェックボックスをONにする

上記の様に入力したらNextボタンをタップしてCellのクラスを作成してください。
ファイルが2つ追加されているはずです。

  • TableViewCell.swift
  • TableViewCell.xib

cellのUIを完成させよう!

f:id:Yumico:20161127170346p:plain
今回はUILabelを二つ用意しました。
右上で四角に囲ってあるidentifierへの入力をお忘れなく。後ほどUITableViewを実装するときに使用します。
任意のidを入力しますが、今回はTableViewCellにしました。xibのファイル名と同名にすると分かりやすくて良いと思います。

TableViewCell.swiftにコードを書いて行こう!

いきなり完成コード全文。
全コピペ対応してますww

import UIKit

class TableViewCell: UITableViewCell {
    //TableViewCell.xibで追加したUILabelを繋げる。
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        //このメソッドは初回1回だけ呼ばれますので、1回だけで良い処理(初期化処理)はここに書きます。
    }
    
    //UITableViewで呼び出す用のメソッド。
    //ここでUILabelのテキストを変更しています。
    func setupCell(words: String?, name: String?) {
        label.text = words ?? "no text"
        nameLabel.text = name ?? "no name"
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        // Configure the view for the selected state
    }
}

これでUITableViewCellの処理は完成です✨

2.UITableViewを実装しよう!

いよいよメインテーマのテーブルビューです!
基本的な実装はとても簡単です。肩の力を抜いていきましょー。

StoryboardにUITableViewを追加する

f:id:Yumico:20161127144440p:plain
こんな感じでUITableViewをUIViewController全体にベタッと貼り付けます。 今回はUINavigationControllerもつけていますが、つけなくても大丈夫です。

f:id:Yumico:20161127144928p:plain:w100:right
ちなみにUINavigationControllerと合わせて使うケース、非常に多いと思います。 今回のようにUITableViewをUIViewController全体に貼り付ける場合、Adjust Scroll View Insetsにチェックを入れてください。でなければ、NavigationBarとTableViewが被ってしまいます。
右の画像はUIViewControllerの設定です。参考にしてください。

ViewController.swiftにUITableViewを実装する

先ほどのStoryboardでテーブルビューを貼り付けたViewControllerのファイルにコードを追加していきます。
完成コードはこちら。

import UIKit

class ViewController: UIViewController {

    //StoryboardからTableViewを繋げておきます。
    @IBOutlet weak var tableView: UITableView!
    
    //テーブルビューで表示させるデータ。表示内容が多い場合などはstractとかクラスを使うケースが多いかもしれませんが
    //今回はArrayの中にDictionaryを入れ子にした設計で簡単に済ませています。
    var data: [[String:String]] {
        get {
            //それぞれ"words"と"name"をkeyにしたdictionaryを生成
            let w1: [String: String] = ["words" : "歳をとればとるほど、動機こそが大切だという核心が深まる。",           "name" : "スティーブ・ジョブズ"]
            let w2: [String: String] = ["words" : "キャリアではない。私の人生なんだ。",                             "name" : "スティーブ・ジョブズ"]
            let w3: [String: String] = ["words" : "何を捨てるかで誇りが問われ、何を守るかで愛情が問われる。",          "name" : "スティーブ・ジョブズ"]
            let w4: [String: String] = ["words" : "昔を振り返るのはここでやめにしよう。大切なのは明日何が起きるかだ。",  "name" : "スティーブ・ジョブズ"]
            let w5: [String: String] = ["words" : "大事なのは自分の心に素直になることだ。",                         "name" : "スティーブ・ジョブズ"]
            //arrayを生成して上記のdictionaryを追加していきます。
            var d: [[String:String]] = []
            d.append(w1)
            d.append(w2)
            d.append(w3)
            d.append(w4)
            d.append(w5)
            return d
        }
    }
        
    override func viewDidLoad() {
        super.viewDidLoad()
        //dataSourceにselfを設定(これを忘れると、まっさらのTableViewが表示されることとなります)
        self.tableView.dataSource = self
        self.tableView.delegate = self
        //このtableviewで使用するcellを登録します。幾つでも登録できます!
        let nib : UINib = UINib(nibName: "TableViewCell", bundle: nil)
        //forCellReuseIdentifierにはxibファイル内で登録したidentifierと同じ文字列を入れましょう!
        self.tableView.register(nib, forCellReuseIdentifier: "TableViewCell")
        //テーブルビューの余分なセパレーターが気になったから消しちゃいます。
        self.tableView.tableFooterView = UIView()
    }
}

extension ViewController: UITableViewDataSource {
//UITableViewをコントロールするDataSourceを実装します。(最低限必要なメソッドは下記2つのみ!)

    //テーブルビューはまずこのメソッドが呼ばれます。セクションの中でデータがいくつ必要かの数を返します。
    //今回はセクションが1つしかないので、dataの数をそのまま返します。
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    //セルに値をセットするにはこのメソッドを使います。
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //indexPathを元にデータを抽出。
        let d : [String : String] = data[indexPath.row]
        //cellを呼び出して値をセットしていきます。(identifierはxibファイルで登録したものと同じ文字列を!)
        let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
        cell.setup(words: d["words"], name: d["name"])
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    //テーブルビューのセルが選択されたときに呼ばれます。
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //選択されたセルの色を解除します。
        tableView.deselectRow(at: indexPath, animated: true)
        
        let d : [String : String] = data[indexPath.row]
        print("「\(d["words"] ?? "no data")」が選択されました!")
    }
}

こちらも全文コピペ対応済みww
貼り付けて実行して色々と触ってみてください。思ったより簡単に実装できるはずです。
不明点だったり、うまくできなかった方はコメントください。
わかる範囲でお答えいたします。