20200330のiOSに関する記事は3件です。

【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で続きを読む

日本で買ったApple Watchで、ECGを使って心電図を撮る夢を見た

タイトルの通り、日本で買ったApple Watch(series4)のECGを有効にしてみました。

この方法は正規の手段ではありません。
2020年3月現在、日本では依然として医療機器認可は下りていません。

あくまで夢の話です。悪しからず

※ちなみに脱獄とかそういう類ではありません。

結論

できました。

image.png
↑販売国コードはJ(Japan:日本)

image.png

アクティベートする方法

アクティベートの手順です。が、注意点

注意点

詳しくは後述しますが、ECGを有効化するには一度正規の手順で有効化されたiCloudアカウントが必要です(便宜上、ここではアクティベートアカウント呼びます)。

はじめの手順として、まずはアメリカでもカナダでもどこでもいいのですが、ECG有効国の友達や家族を作りましょう。
僕みたいにブロンドの女の子と縁がない人は、指を加えて次に行ってみよう。

手順

  1. watchOS 6.2およびiOS13.4へのアップグレード
  2. いつも使用している自分のiCloudアカウントからログアウトする
  3. アクティベートアカウントにログイン
  4. iCloudの同期項目のうち、ヘルスケアのみを同期する
  5. 同期が完了すると、AppleWatchにECGが表示される
  6. 自分の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のアクティベーション履歴に基づいて利用の可否が判定されます。
図にするとこんな感じ。

image.png

image.png

つまり、一度でも有効化された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_mpnfalseに戻る可能性もありますので、あくまでiOS13.4時点の情報として残しておきます!


  1. 正確にはiOS13.4 beta2 

  2. 設定からソース元デバイスを削除するなどの回避策はありますが、それでもオススメできません 

  3. どうしてもという方は、こちらの記事をご覧ください(https://note.com/sskmy1024y/n/n74b9d88bd1b7

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