- 投稿日:2020-03-30T13:07:25+09:00
JTAppleCalendar(v8.0.3)をもう少しさわってみる
はじめに
以前に書いた記事の続きです。
https://qiita.com/ta9yamakawa/items/850aada73153ba860ad9カレンダーの表示をとりあえず表示するとこまで行ったので、さらにカスタマイズしてよりカレンダーっぽくしてみます。
公式チュートリアルからよく使いそうなところを掻い摘んでやってみます。
※ 以前のカレンダーでは5行表示をしていますが、6行表示に変更しています。実装
別月の日にちを区別する
例えば4月の表示をしたいとき、月初の週に3月の31日が入ったり、月末の週に4月の1日が入ったりすることがあります。
この時、別月の日にちだと見ただけでわかるようにします。MyCalendarViewCell.swiftfunc 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.swiftoverride func awakeFromNib() { /* 省略 */ calendarView.scrollDirection = .horizontal // 横向きにスクロール calendarView.scrollingMode = .stopAtEachCalendarFrame // 月単位でのスクロール /* 省略 */ }日付を選択した時のアクションを追加する
選択状態によって、その日にちのセルを表示/非表示を切り替えるようにしてみます。下記メソッドを作成し、configureメソッドの中で呼び出します。
MyCalendarViewCell.swiftfunc handleCellSelected(cellState: CellState) { if cellState.isSelected { isHidden = true } else { isHidden = false } }日付セルが選択されたときと選択解除されたときにもConfigureの処理を呼ぶようにします。
MyCalendarView.swiftfunc 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系に書き換えながら進めてみましたが、日付の選択やスクロールの設定は比較的簡単にできました。
参照
- 投稿日:2020-03-30T13:07:25+09:00
JTAppleCalendar(v8.0.3)をもう少し触ってみる
はじめに
以前に書いた記事の続きです。
https://qiita.com/ta9yamakawa/items/850aada73153ba860ad9カレンダーの表示をとりあえず表示するとこまで行ったので、さらにカスタマイズしてよりカレンダーっぽくしてみます。
公式チュートリアルからよく使いそうなところを掻い摘んでやってみます。
※ 以前のカレンダーでは5行表示をしていますが、6行表示に変更しています。実装
別月の日にちを区別する
例えば4月の表示をしたいとき、月初の週に3月の31日が入ったり、月末の週に4月の1日が入ったりすることがあります。
この時、別月の日にちだと見ただけでわかるようにします。MyCalendarViewCell.swiftfunc 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.swiftoverride func awakeFromNib() { /* 省略 */ calendarView.scrollDirection = .horizontal // 横向きにスクロール calendarView.scrollingMode = .stopAtEachCalendarFrame // 月単位でのスクロール /* 省略 */ }日付を選択した時のアクションを追加する
選択状態によって、その日にちのセルを表示/非表示を切り替えるようにしてみます。下記メソッドを作成し、configureメソッドの中で呼び出します。
MyCalendarViewCell.swiftfunc handleCellSelected(cellState: CellState) { if cellState.isSelected { isHidden = true } else { isHidden = false } }日付セルが選択されたときと選択解除されたときにもConfigureの処理を呼ぶようにします。
MyCalendarView.swiftfunc 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系に書き換えながら進めてみましたが、日付の選択やスクロールの設定は比較的簡単にできました。
参照
- 投稿日:2020-03-30T12:39:32+09:00
【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
検証環境
Mac: 10.14.4
XCode: 10.2
Swift: 5.0
参考ドキュメント
- 投稿日:2020-03-30T02:26:00+09:00
【SceneKit】3Dモデル(アニメーションつき)の再生速度を変更する
はじめに
アニメーションつき3Dモデルの再生速度をを可変にしたかったので、そのメモ。
実行環境
- Xcode 11.2.1
- MagicaVoxel 0.99.4
- Mixamo
- iPhoneXR(iOS 12.1.4)
やりたいこと
Mixamoでアニメーションをつけた3Dモデルの、再生速度(テンポ)をゆっくりにしたり、速くしたり、任意に変更してみたかった。
再生速度をどこで変更できるか?
読み込んだ3Dモデル(Salsa Dancing.dae)から変換したSCNファイル(salsa.scn)を開いて、ボーンの設定(mixamorig_Hips)を選択すると、右下の「Animation Settings」に「speed」という項目があって、この数値を変更して画面下の再生ボタンを押すと再生速度が変わります(speed:1 が定速)。
再生速度をプログラムで制御するには?
いろいろググってみるとSCNAnimatable の animation(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") }完成
再生ボタンでアニメーションを再生/停止。スライダーでアニメーションの再生速度が変わります。
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 ごと差し替えるんじゃなくて、アニメーションだけ再設定すればいけそうな気がする。それはまた追って試してみます。
もっといい方法があるよ!という方はご指摘頂けるとありがたいです。