注: 自作文字版は審査に落ちます。自作文字版はあくまで遊びです。
前書き
初めまして! iOSアプリのエンジニアでVTuberになれるアプリを開発中の、蒼黯(あおぐろ)です。
https://twitter.com/pc_blue_screen
WWDC21、iOS15やwatchOS 8などが発表されましたね。
早速ですが、watchOS 8でアプリが常時表示できる新機能が発表されたので、試していきましょう。(本当はSharePlayやりたかったけど、署名サーバーが未対応だった...)
Appleとの秘密保持契約に基づきベータ版のスクリーンショット投稿は行いません。
記事内容もAppleが一般向けに公開している資料に書かれてることを記事化しています。
環境
- macOS BigSur 11.4
- Xcode 13 Beta 1
- iOS 15 Developer Beta 1
- watchOS 8 Beta
常時表示
Apple Watch Series 5以降(SE除く)の常時表示がwatchOS 8でデベロッパに解放された。
音楽再生やワークアウトなどのセッション時に限られるものの、
事前設定
常時表示を実現するために、まずはInfo.plistでWKSupportsAlwaysOnDisplayキーをtrueにする必要がある。
Info.plistにWKSupportsAlwaysOnDisplayと打つか、一覧からApp Supports Always On Display (Watch)を選択すれば出てくるので、YESを選択する。
状態の判別
@Environment(.scenePhase) private var scenePhase で判定できる。
戻り値はactive/inactive/backgroundの3つで、意味はそのまま。
if scenePhase == .active {
Text("通常表示")
} else {
Text("腕を下げた時")
}
※TimelineViewというViewでswitch分岐も作れるが、現時点ではバグで再現できていない。
情報を隠す
手首を下げた時に表示するべきでないプライベートな情報は**.privacySensitive()** で隠す。
もしくは**@Environment(.redactionReasons) var redactionReasons**を呼んでおき、
if !redactionReasons.contains(.privacy) {
Text("5000兆円欲しい")
}
のようにする。
まとめ
struct ContentView: View {
@Environment(\.scenePhase) private var scenePhase
@Environment(\.redactionReasons) var redactionReasons
VStack {
if scenePhase == .active {
Text("腕を上げた時")
} else {
Text("腕を下げた時")
}
Text("Privacy")
.privacySensitive()
}
}
文字版を作る
上の仕組みを悪用すれば、簡易的に文字版も作ることができます。
(フォントは廻想体お借りしました! https://moji-waku.com/kaiso/)
右上の時計表示はプライベートAPIじゃないと消せないっぽいです。
import SwiftUI
struct ContentView: View {
@Environment(\.scenePhase) private var scenePhase
@State var D = Calendar.current.component(.day, from: Date())
@State var H = Calendar.current.component(.hour, from: Date())
@State var M = Calendar.current.component(.minute, from: Date())
@State var S = Calendar.current.component(.second, from: Date())
let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
var body: some View {
VStack {
ZStack{
Circle()
.trim(from: 0, to: CGFloat(Double(self.H) / 30))
.stroke(Color.blue,style: StrokeStyle(lineWidth: 10, lineCap: .square, lineJoin: .bevel))
.rotationEffect(.degrees(-90))
.rotation3DEffect(Angle(degrees: 12), axis: (x: 0, y: 0, z: 0))
.frame(width: 120, height: 120)
.animation(Animation.linear, value: CGFloat(Double(self.H) / 30))
Circle()
.trim(from: 0, to: CGFloat(Double(self.M) / 60.0))
.stroke(Color.red,style: StrokeStyle(lineWidth: 10, lineCap: .square, lineJoin: .bevel))
.rotationEffect(.degrees(-90))
.rotation3DEffect(Angle(degrees: 60), axis: (x: 0, y: 0, z: 0))
.frame(width: 100, height: 100)
.animation(Animation.linear, value: CGFloat(Double(self.M) / 60.0))
if scenePhase == .active {
Circle()
.trim(from: 0, to: CGFloat(Double(self.S) / 60.0))
.stroke(Color.orange,style: StrokeStyle(lineWidth: 10, lineCap: .square, lineJoin: .bevel))
.rotationEffect(.degrees(-90))
.rotation3DEffect(Angle(degrees: 80), axis: (x: 0, y: 0, z: 0))
.frame(width: 80, height: 80)
.animation(Animation.linear, value: CGFloat(Double(self.S) / 60.0))
}
Text("\(self.D)")
.font(.custom("Kaisotai-Next-UP-B", size: 30))
}
}.onReceive(timer){ _ in
self.H = Calendar.current.component(.hour, from: Date())
self.M = Calendar.current.component(.minute, from: Date())
self.S = Calendar.current.component(.second, from: Date())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
宣伝
# 参考 Designing Your App for the Always On State - Apple Developer https://developer.apple.com/documentation/watchkit/designing_your_app_for_the_always_on_state【更新】開発中のVTuberアプリについて#Vtuber #Vtuberさんと繋がりたい pic.twitter.com/g9Q2IWMxQs
— 蒼黯@VTuberアプリ開発中 (@pc_blue_screen) May 8, 2021