- 投稿日: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 ごと差し替えるんじゃなくて、アニメーションだけ再設定すればいけそうな気がする。それはまた追って試してみます。
もっといい方法があるよ!という方はご指摘頂けるとありがたいです。
- 投稿日:2020-03-30T01:41:20+09:00
日本で買ったApple Watchで、ECGを使って心電図を撮る夢を見た
タイトルの通り、日本で買ったApple Watch(series4)のECGを有効にしてみました。
この方法は正規の手段ではありません。 2020年3月現在、日本では依然として医療機器認可は下りていません。あくまで夢の話です。悪しからず
※ちなみに脱獄とかそういう類ではありません。
結論
できました。
アクティベートする方法
アクティベートの手順です。が、注意点。
注意点
詳しくは後述しますが、ECGを有効化するには一度正規の手順で有効化されたiCloudアカウントが必要です(便宜上、ここでは
アクティベートアカウント
呼びます)。はじめの手順として、まずはアメリカでもカナダでもどこでもいいのですが、ECG有効国の友達や家族を作りましょう。
僕みたいにブロンドの女の子と縁がない人は、指を加えて次に行ってみよう。手順
- watchOS 6.2およびiOS13.4へのアップグレード
- いつも使用している自分のiCloudアカウントからログアウトする
アクティベートアカウント
にログイン- iCloudの同期項目のうち、ヘルスケアのみを同期する
- 同期が完了すると、AppleWatchにECGが表示される
- 自分のiCloudアカウントに戻す
以上です。
仕組み
ここからはコードを絡めた詳しい説明になるので、興味ない人は飛ばしてください。
なお、この情報の出典元および提供記事はこちらです。自分も完全に理解しているわけではないので、間違っていたらすみません。HealthKitにある2つのクラス
HealthKitには、2つのクラスがあります。それぞれの違いは、簡単にいうとこんな感じ。
クラス名 リージョンチェック HKMPNDeviceRegionFeatureSupportedStateProvider 行われる HKNonMPNDeviceRegionFeatureSupportedStateProvider 行われない +(id)deviceRegionFeatureSupportedStateProviderForCompanionDevice:(id)device { BOOL checkIgnored = [self isCompanionRegionCheckEnabledForDevice:device]; Class class = [HKMPNDeviceRegionFeatureSupportedStateProvider class]; if (checkIgnored != NO) { class = [HKNonMPNDeviceRegionFeatureSupportedStateProvider class]; } return [class isCompanionRegionCheckEnabledForDevice:device]; } +(bool)isCompanionRegionCheckEnabledForDevice:(void *)device { BOOL ignoresMPN = _os_feature_enabled_impl("HeartRhythm", "ecg_app_install_ignores_mpn"); // ←ココ!!! NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"03C7A646-DB1E-404B-B393-033E5496A383"]; BOOL supportUUID = [device supportsCapability:uuid]; return ignoresMPN && supportUUID }
ecg_app_install_ignores_mpn
が有効になっている場合、HKNonMPNDeviceRegionFeatureSupportedStateProvider
が呼び出されるため、リージョンチェックが無視されます。そして、この
ecg_app_install_ignores_mpn
は/System/Library/FeatureFlags/Domain/HeartRhythm.plist
にて定義されています。
このファイルはiOSのシステム領域にあるため、基本的にユーザーは変更することができません。iOS13.4での変更
先日公開されたiOS13.4およびWatchOS6.2にてこの
HeartRhythm.plist
に変更が加わりました1。HeartRhythm.plist<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>ecg_app_install_ignores_mpn</key> <dict> <key>Enabled</key> - <false /> + <true/> <key>DisplayName</key> <string>Move Off of MPN</string> </dict> </dict> </plist>この変更により、iOS13.4ではリージョンチェックがバイパスされている状態にあります。
リージョンチェックがバイパスされると……?
このリージョンチェックがバイパスされると、デバイスのリージョンに関係なく、iCloudのアクティベーション履歴に基づいて利用の可否が判定されます。
図にするとこんな感じ。つまり、一度でも有効化されたiCloudアカウントでログインすることによって、デバイス側のECGが有効化されるわけです。
そして有効化されたまま元のiCloudアカウントに戻ると、元のアカウントでもアクティベートされて利用可能状態になります。このような仕組みによって、デバイスの購入国に関係なくECGを有効化できるわけです。
アクティベートアカウントなんて持ってないよ
まあ、当然ですよね。
一度でもアクティベートしたことあるならそのまま使えばいいわけですし。仮にお知り合いにECGを正規で有効化されている方がいたとしても、この手法のためにアカウントを借りるのをお願いすることはお勧めできません。
iCloudアカウントには、写真やヘルスケアデータなども同期されているため、個人情報の流出につながります。100歩譲って家族だとしても、オススメはできないですね。
iCloudに同期する際にヘルスケアデータが結合されてしまい2、借りる側も貸す側もデメリットしかありません。……はい、諦めよう3。
〆
ECG機能がApple Watchに搭載されてからもうすぐ2年が経ちますが、日本での目処は立っていないようですね。
噂だと日本の医療器具認可は手続きが厳しいらしく、Appleも半ば諦めているとか……。とは言え、今までは海外で購入したApple Watchが必要だったり、有効国内でアクティベートする必要があったりと、何かと制約が多かったECG。
ネットにはECGの為の代理購入サイトもあり、わざわざ海外のApple Watchを取り寄せる方も多かったのではないでしょうか?
そんな中、日本で購入したApple WatchでもECGが利用できるようになったので、ちょっといい夢を見られた気分です。ちなみにこの情報元の記事を書いたHiraku氏曰く、これはバグの一種だとのこと。まああれだけ頑なに制限かけていた機能ですから、こんな簡単な手法で有効化できたらまずいですよね……。
というわけで、今後のiOSで
ecg_app_install_ignores_mpn
がfalse
に戻る可能性もありますので、あくまでiOS13.4
時点の情報として残しておきます!
正確には
iOS13.4 beta2
↩設定からソース元デバイスを削除するなどの回避策はありますが、それでもオススメできません ↩
どうしてもという方は、こちらの記事をご覧ください(https://note.com/sskmy1024y/n/n74b9d88bd1b7) ↩