20200330のSwiftに関する記事は4件です。

JTAppleCalendar(v8.0.3)をもう少しさわってみる

はじめに

以前に書いた記事の続きです。
https://qiita.com/ta9yamakawa/items/850aada73153ba860ad9

カレンダーの表示をとりあえず表示するとこまで行ったので、さらにカスタマイズしてよりカレンダーっぽくしてみます。
公式チュートリアルからよく使いそうなところを掻い摘んでやってみます。
※ 以前のカレンダーでは5行表示をしていますが、6行表示に変更しています。

実装

別月の日にちを区別する

例えば4月の表示をしたいとき、月初の週に3月の31日が入ったり、月末の週に4月の1日が入ったりすることがあります。
この時、別月の日にちだと見ただけでわかるようにします。

MyCalendarViewCell.swift
    func configure(cellState: CellState) {

        self.titleLabel.text = cellState.text
        configureTextColor(cellState: cellState) // 追加
    }

    /// セルの文字色を設定
    func configureTextColor(cellState: CellState) {
       if cellState.dateBelongsTo == .thisMonth {
          titleLabel.textColor = UIColor.black
       } else {
          titleLabel.textColor = UIColor.gray
       }
    }

こうすると、下記画像の青色の枠のようになります。

ちなみに消すこともできます。

if cellState.dateBelongsTo == .thisMonth {
   isHidden = false
} else {
   isHidden = true
}

スクロールの制御をする

デフォルトの設定だと縦にスクロールされるので横にスクロールされるようにします。
また、カレンダーが連続してスクロールされるので、スクロールするときにひと月単位で見たいときは下記の設定をします。

MyCalendarView.swift
    override func awakeFromNib() {
        /*
        省略
        */
        calendarView.scrollDirection = .horizontal // 横向きにスクロール
        calendarView.scrollingMode = .stopAtEachCalendarFrame // 月単位でのスクロール
        /*
        省略
        */
    }

日付を選択した時のアクションを追加する

選択状態によって、その日にちのセルを表示/非表示を切り替えるようにしてみます。下記メソッドを作成し、configureメソッドの中で呼び出します。

MyCalendarViewCell.swift
    func handleCellSelected(cellState: CellState) {
        if cellState.isSelected {
            isHidden = true
        } else {
            isHidden = false
        }
    }

日付セルが選択されたときと選択解除されたときにもConfigureの処理を呼ぶようにします。

MyCalendarView.swift
    func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
        guard let cell = cell as? MyCalendarViewCell else {
            return
        }
        cell.configure(cellState: cellState)
    }

    func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
        guard let cell = cell as? MyCalendarViewCell else {
            return
        }
        cell.configure(cellState: cellState)
    }

成功すると以下の画像のようになります。選択した日にちの部分が非表示になりましたね。

おわりに

公式チュートリアル(7.1.7)の4までを8系に書き換えながら進めてみましたが、日付の選択やスクロールの設定は比較的簡単にできました。

参照

  1. JTAppleCalendar(v8.0.3)を簡単にさわってみる
  2. https://patchthecode.com/
  3. Cell not selected right away on tap
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JTAppleCalendar(v8.0.3)をもう少し触ってみる

はじめに

以前に書いた記事の続きです。
https://qiita.com/ta9yamakawa/items/850aada73153ba860ad9

カレンダーの表示をとりあえず表示するとこまで行ったので、さらにカスタマイズしてよりカレンダーっぽくしてみます。
公式チュートリアルからよく使いそうなところを掻い摘んでやってみます。
※ 以前のカレンダーでは5行表示をしていますが、6行表示に変更しています。

実装

別月の日にちを区別する

例えば4月の表示をしたいとき、月初の週に3月の31日が入ったり、月末の週に4月の1日が入ったりすることがあります。
この時、別月の日にちだと見ただけでわかるようにします。

MyCalendarViewCell.swift
    func configure(cellState: CellState) {

        self.titleLabel.text = cellState.text
        configureTextColor(cellState: cellState) // 追加
    }

    /// セルの文字色を設定
    func configureTextColor(cellState: CellState) {
       if cellState.dateBelongsTo == .thisMonth {
          titleLabel.textColor = UIColor.black
       } else {
          titleLabel.textColor = UIColor.gray
       }
    }

こうすると、下記画像の青色の枠のようになります。

ちなみに消すこともできます。

if cellState.dateBelongsTo == .thisMonth {
   isHidden = false
} else {
   isHidden = true
}

スクロールの制御をする

デフォルトの設定だと縦にスクロールされるので横にスクロールされるようにします。
また、カレンダーが連続してスクロールされるので、スクロールするときにひと月単位で見たいときは下記の設定をします。

MyCalendarView.swift
    override func awakeFromNib() {
        /*
        省略
        */
        calendarView.scrollDirection = .horizontal // 横向きにスクロール
        calendarView.scrollingMode = .stopAtEachCalendarFrame // 月単位でのスクロール
        /*
        省略
        */
    }

日付を選択した時のアクションを追加する

選択状態によって、その日にちのセルを表示/非表示を切り替えるようにしてみます。下記メソッドを作成し、configureメソッドの中で呼び出します。

MyCalendarViewCell.swift
    func handleCellSelected(cellState: CellState) {
        if cellState.isSelected {
            isHidden = true
        } else {
            isHidden = false
        }
    }

日付セルが選択されたときと選択解除されたときにもConfigureの処理を呼ぶようにします。

MyCalendarView.swift
    func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
        guard let cell = cell as? MyCalendarViewCell else {
            return
        }
        cell.configure(cellState: cellState)
    }

    func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
        guard let cell = cell as? MyCalendarViewCell else {
            return
        }
        cell.configure(cellState: cellState)
    }

成功すると以下の画像のようになります。選択した日にちの部分が非表示になりましたね。

おわりに

公式チュートリアル(7.1.7)の4までを8系に書き換えながら進めてみましたが、日付の選択やスクロールの設定は比較的簡単にできました。

参照

  1. JTAppleCalendar(v8.0.3)を簡単にさわってみる
  2. https://patchthecode.com/
  3. Cell not selected right away on tap
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】CAGradientLayer の透過が濁る

下記ブログの転載です!
https://rc-code.info/ios/post-305/

iOSにて、CAGradientLayer を使って透過を含むグラデーションを行った際、濁る現象があったので対応の備忘録。
 

CAGradientLayer の使い方

CAGradientLayer の基本的な使い方については、こちら を参照のこと。
 

CAGradientLayer に透過を利用すると色が濁る

当然のことながら CAGradientLayer はグラデーションを作成するレイヤーです。
「そこで、透過から赤へのグラデーションを作ろう!」と思った時、グラデーションが濁る(中間色がグレーになる)ことに気がつきました。
下記がその時の画像です。

汚い透過グラデーション

この時僕が作成したコードは下記のようなものでした。

        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [UIColor.clear.cgColor, UIColor.red.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
        gradientLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        view.layer.addSublayer(gradientLayer)

 
一見、コード上はあっているように思えますが、実は UIColor.clear.cgColor が正しくありませんでした!

先に結論を申し上げると、この部分を UIColor.red.withAlphaComponent(0).cgColor に修正すると綺麗に透過グラデーションがかかります。

綺麗な透過グラデーション

全体コードでは下記です。

        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [UIColor.red.withAlphaComponent(0).cgColor, UIColor.red.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
        gradientLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        view.layer.addSublayer(gradientLayer)

考えてみれば当然で、プログラム上には何色の透過から赤に向かえば良いかが明記されていません。
つまり UIColor.clear.cgColor は黒の透過 (おそらくマスクをかけた際のアルファ計算の基準色だから) として扱われているので、赤へのグラデーションにグレーが混じるということだと思います。

「なんか透明のグラデーション作ったけど綺麗じゃないなぁ」と思ったら参考にしてみてください。

検証Playground

検証Playground Git
 

検証環境

Mac: 10.14.4
XCode: 10.2
Swift: 5.0
 

参考ドキュメント

公式 CAGradientLayer ドキュメント
 
 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SceneKit】3Dモデル(アニメーションつき)の再生速度を変更する

はじめに

アニメーションつき3Dモデルの再生速度をを可変にしたかったので、そのメモ。

実行環境

やりたいこと

Mixamoでアニメーションをつけた3Dモデルの、再生速度(テンポ)をゆっくりにしたり、速くしたり、任意に変更してみたかった。

再生速度をどこで変更できるか?

読み込んだ3Dモデル(Salsa Dancing.dae)から変換したSCNファイル(salsa.scn)を開いて、ボーンの設定(mixamorig_Hips)を選択すると、右下の「Animation Settings」に「speed」という項目があって、この数値を変更して画面下の再生ボタンを押すと再生速度が変わります(speed:1 が定速)。
cap01.png

再生速度をプログラムで制御するには?

いろいろググってみるとSCNAnimatableanimation(forKey:) を使うらしいのですが、書いてみてもそもそもエラーが出て動かない。。。

更に調べてみると、こんな記事が。

Alternative to animation(forKey:) (now deprecated)?

ざっと(Google先生が)訳してみると「CAAnimationで動作するanimation(forKey :)およびその姉妹メソッドはiOS11で廃止になったので、新しく導入された SCNAnimationPlayerを使え」ということらしい。SCNファイルで確認した「speed」というプロパティもありますね。

アニメーション速度の設定

具体的には、以下のような手順になるようです。

1.SCNScene から 3Dモデルのnodeを取り出す。
2.daeファイルから SCNAnimationPlayerr.loadAnimation でアニメーションを取り出す。
3.アニメーションの再生速度を設定して、上記1.のnodeに node.addAnimationPlayer でアニメーションを再設定する。
4. SCNScene に 3Dモデルのnodeを追加する(たぶん上書きになる?)

アニメーション速度の設定
        let scene = SCNScene(named: "art.scnassets/salsa.scn")!

        // ノード作成
        let node = scene.rootNode.childNode(withName: "salsa", recursively: true)!

        //アニメーションをSCNAnimationPlayerで取りだして、設定変更してnode に再設定する 2020.3.14
        danceAnimation = SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Salsa Dancing.dae")

        //speed:2
        danceAnimation.speed = 2

        danceAnimation.stop()
        node.addAnimationPlayer(danceAnimation, forKey: "dance")

        // アイテムの配置
        node.position = SCNVector3(0, 0, 0)
        node.scale = SCNVector3(1, 1, 1)
        scene.rootNode.addChildNode(node)

アニメーション速度を可変にしてみる

UISliderを使ってアニメーションの再生速度を可変にしてみました。
スライダーによるspeedの範囲は、0.1-3.0にしました。

先程との違いは、以下の点です。

  • nodeは、配置済のSCNView内のシーン(self.mySceneView.scene)から取り出す。
  • アニメーションは保存済の変数(danceAnimationを使う。
  • アニメーションを追加する前に、設定済のアニメーションを削除する(node.removeAllActions())。
アニメーション速度の設定
    //スライダー:初期化
    func initSlider(val:Float){
        speedSlider.minimumValue = 0.1
        speedSlider.maximumValue = 3.0
        speedSlider.value = val
    }

    @IBAction func dragSlider(_ sender: UISlider) {

        print("+++value:",sender.value)

        // ノード作成
        let node = self.mySceneView.scene!.rootNode.childNode(withName: "salsa", recursively: true)!

        //speed:value
        danceAnimation.speed = CGFloat(sender.value)
        danceAnimation.stop() 

        node.removeAllActions()
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
    }

完成

再生ボタンでアニメーションを再生/停止。スライダーでアニメーションの再生速度が変わります。
スクリーンショット 2020-03-30 2.01.43.png

ViewContoller.swift全体のコードです。

ViewContoller.swift
//
//  ViewController.swift
//  ChangeTempoDance
//
//  Created by c-geru on 2020/03/22.
//  Copyright © 2020 c-geru. All rights reserved.
//

import UIKit
import SceneKit

class ViewController: UIViewController {

    @IBOutlet weak var mySceneView: SCNView!

    @IBOutlet weak var playButton: UIBarButtonItem!

    @IBOutlet weak var speedSlider: UISlider!

    @IBOutlet weak var toolBar: UIToolbar!

    var danceAnimation: SCNAnimationPlayer!
    var isPlay: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let scene = SCNScene(named: "art.scnassets/salsa.scn")!


        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)

        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 1, z: 5)

        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)

        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)

        // ノード作成
        let node = scene.rootNode.childNode(withName: "salsa", recursively: true)!

        //アニメーションをSCNAnimationPlayerで取りだして、設定変更してnode に再設定する 2020.3.14
        danceAnimation = SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Salsa Dancing.dae")

        //speed:2
        danceAnimation.speed = 2

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.addAnimationPlayer(danceAnimation, forKey: "dance")

        initSlider(val: Float(danceAnimation!.speed))

        // アイテムの配置
        node.position = SCNVector3(0, 0, 0)
        node.scale = SCNVector3(1, 1, 1)
        scene.rootNode.addChildNode(node)

        // set the scene to the view
        self.mySceneView!.scene = scene

        // configure the view
        self.mySceneView!.backgroundColor = UIColor.clear
    }

    //スライダー:初期化
    func initSlider(val:Float){
        speedSlider.minimumValue = 0.1
        speedSlider.maximumValue = 3.0
        speedSlider.value = val
    }

    @IBAction func dragSlider(_ sender: UISlider) {

        print("+++value:",sender.value)

        // ノード作成
        let node = self.mySceneView.scene!.rootNode.childNode(withName: "salsa", recursively: true)!

        //speed:value
        danceAnimation.speed = CGFloat(sender.value)

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.removeAllActions()
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
    }

    @IBAction func touchUpSlider(_ sender: UISlider) {
        if (isPlay){
           danceAnimation.play()
        }

    }

    @IBAction func tapPlayButton(_ sender: UIBarButtonItem) {
        if (isPlay){
            danceAnimation.stop()
            isPlay = false
        } else {
            danceAnimation.play()
            isPlay = true
        }

        //開始ボタン:表示切替
        changePlayButton()
    }

    //開始ボタン:表示切替
    func changePlayButton() {
        var items = toolBar.items!
        var btnItem: UIBarButtonItem!

        if (isPlay) {
            print("----pause")
            btnItem = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action: #selector(self.tapPlayButton(_:)))
        } else {
            print("----play")
            btnItem = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(self.tapPlayButton(_:)))
        }

        items[0] = btnItem
        toolBar.setItems(items, animated: false)
    }

}

extension SCNAnimationPlayer {
    class func loadAnimation(fromSceneNamed sceneName: String) -> SCNAnimationPlayer {
        let scene = SCNScene( named: sceneName )!
        // find top level animation
        var animationPlayer: SCNAnimationPlayer! = nil
        scene.rootNode.enumerateChildNodes { (child, stop) in
            if !child.animationKeys.isEmpty {
                animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
                stop.pointee = true
            }
        }
        return animationPlayer
    }
}

まとめ

こうやってアニメーションを差し替えられるのであれば、以前書いた3Dアニメーションの切り替えも、node ごと差し替えるんじゃなくて、アニメーションだけ再設定すればいけそうな気がする。それはまた追って試してみます。

もっといい方法があるよ!という方はご指摘頂けるとありがたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む