Guild icon
swift-developers-japan
開発環境, ライブラリ / swiftui
Avatar
@_functionBuilder
8:05 PM
なるほど
8:06 PM
しかしアノテーションマジックが一気に増えましたね。
Avatar
Provide basic support for function builders, which allow the convenient creation of eDSLs for complex lists and hierarchies. An evolution pitch for this is forthcoming. That pitch proposes a somew...
Avatar
omochimetaru 6/3/2019 8:07 PM
43分前w
8:09 PM
ひえーなんだこれ
Avatar
omochimetaru 6/3/2019 8:10 PM
DSLみたいになってて
8:10 PM
サブビューを捕捉してるところは
8:10 PM
ただの型じゃなくてこれを使って
8:10 PM
解釈を動的にしょりしてるのか
8:10 PM
すごい機能きたな。
Avatar
Stackのinitの引数は @ViewBuilder content: () -> Content ってなってる
8:11 PM
これによってreturn書かなくてよくなってるのか (edited)
Avatar
omochimetaru 6/3/2019 8:12 PM
てかなんだこれ、ビューの子供も型に埋め込まれてる?
Avatar
焼きこまれてますね
Avatar
ViewBuilderでは今の所10個までっぽい static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>
Avatar
TupleViewがあってそこに焼き込まれる
Avatar
omochimetaru 6/3/2019 8:13 PM
足りない時は包みを増やして回避かな
8:14 PM
if-elseとか、ifだけで条件によって表示されるViewもこうやって制御されてると
8:15 PM
Provide basic support for function builders, which allow the convenient creation of eDSLs for complex lists and hierarchies. An evolution pitch for this is forthcoming. That pitch proposes a somew...
Avatar
public protocol Identifiable 差分計算みっけ
8:17 PM
Listだと差分計算走りますね、HStackとかは多分なし。
Avatar
一方AndroidのDeclarative UIのComposeもコード生成で色々やってる http://intelligiblebabble.com/compose-from-first-principles/
Thousands of Developers from around the world attended Google I/O 2019 earlier this month. It was a particularly exciting I/O for me, as it…
Avatar
omochimetaru 6/3/2019 8:18 PM
それが多分Reactのkeyアトリビュートと同じことをやるやつかな
Avatar
そうっぽい
Avatar
こういうのもちゃんとある
Avatar
Sheetが気になる…
Avatar
セミモーダル的なやつですかね
Avatar
ですかねーApplePayとかAppStoreの決済時に出るSheetの大元みたいのっぽそう
Avatar
extension Optional : Publisher where Wrapped : Publisher { ええんかw
Avatar
SheetはmacOSだけだからセミモーダルとはまたちょっと違いそうでした
😫 1
Avatar
It's always been a goal of Swift to support declarative programming, and the language can be quite good for it, but some kinds of "declaration" fit better into the current language than others. In particular, heterogeneous trees with a lot of hard-coded structure — such as...
👀 4
Avatar
Kishikawa Katsumi 6/4/2019 12:24 AM
SwiftUIでビューコントローラはどうなるんだろう?
Avatar
仮想Viewが画面遷移も担っていて UIHostingControllerに渡して展開されるので、メジャーな機能は仮想Viewで使えて、そうで無いものはUIKit世界で処理、になると思います。SwiftUIの世界ではUIViewとUIViewControllerの区別はないように思える
Avatar
omochimetaru 6/4/2019 4:25 AM
これまでVCに書いてたような状態制御はViewのstateに埋め込むのか、それともその外側でEnvironmentの方にオブジェクトを置くのか??
Avatar
使い分けだと思う
Avatar
この VStack のtrailing closure で if 文かけるのなぜかわからないのですが説明できる人います? var body: some View { VStack(alignment: .center) { Text("1") if true { Text("2") } Text("3") } }
Avatar
omochimetaru 6/4/2019 4:40 AM
function builderのbuildIf
4:40 AM
そのクロージャ?の中身のコードは
4:41 AM
こいつのメソッド呼び出しにコンパイルされる
Avatar
まだよく分かってないけど、Environmentはグローバル・もしくは親子間での共有の状態、@Stateはローカルの状態、という感じかなと思ってます (edited)
Avatar
omochimetaru 6/4/2019 4:44 AM
buildBlockの呼び出しに変換されて、 TupleView<Text,Text?,Text>になるっぽい
4:45 AM
なるほど→envとstate
Avatar
その変換ってどうやって決定しているのかわからなくて。。。要素2つ目のテキストが Text? になるってコンパイル時にわからなくない。というかTupleの要素数決定できないなと思っていて。それが
こいつのメソッド呼び出しにコンパイルされる
って ViewBuilderがよしなにやってくれるって感じなんですかね
Avatar
omochimetaru 6/4/2019 4:46 AM
if文があると、buildIfになるんだと思います。
4:47 AM
IfだからoptionalになるのでText?
4:47 AM
なので、数は変動しない。
Avatar
omochimetaru 6/4/2019 4:47 AM
そう
4:49 AM
ifelseだとbuildEitherになって
4:49 AM
ConditionalContent<TrueContent, FalseContent>
Avatar
なんとなくわかりました。あざすあざす。これちなみに if をかいて buildif が使われる言語機能?って心当たりがあるプロポーザルとかあります? きっとSwiftUIだから特別構文を用意しているとかではないと思っているのですが
Avatar
発表の4時間前に出てたやつですね、DSL用にアノテーションが追加される
4:53 AM
585199874728001566
4:53 AM
ありゃ
Avatar
omochimetaru 6/4/2019 4:53 AM
Provide basic support for function builders, which allow the convenient creation of eDSLs for complex lists and hierarchies. An evolution pitch for this is forthcoming. That pitch proposes a somew...
Avatar
omochimetaru 6/4/2019 4:53 AM
プルリクがこれで、フォーラムが
4:54 AM
It's always been a goal of Swift to support declarative programming, and the language can be quite good for it, but some kinds of "declaration" fit better into the current language than others. In particular, heterogeneous trees with a lot of hard-coded structure — such as...
4:54 AM
Apple、裏で進めてた
Avatar
なるほど。これ事前に会話に出てたんですね。少しさかのぼったらワカッタ
Avatar
omochimetaru 6/4/2019 4:54 AM
いきなりさっき表舞台に出てきた言語機能
Avatar
proposalにどういう風にコードが変換されるかの例も書かれてます (edited)
4:54 AM
div { var v0_opt: [HTML]? if useChapterTitles { let v0: [HTML] = HTMLBuilder.buildExpression(h1(chapter + "1. Loomings.")) v0_opt = v0 } let v0_result = HTMLBuilder.buildOptional(v0_opt) let v1 = HTMLBuilder.buildExpression(p { "Call me Ishmael. Some years ago" }) let v2 = HTMLBuilder.buildExpression(p { "There is now your insular city" }) return HTMLBuilder.buildBlock(v0_result, v1, v2) }
Avatar
omochimetaru 6/4/2019 4:55 AM
HTMLだ
Avatar
いまいち上手くいかないと話題 #swift2 (edited)
4:56 AM
いまいち上手くいかないと話題 #swift-2
Avatar
omochimetaru 6/4/2019 4:56 AM
オープンに進めながら秘密の進捗もあるのAppleやばいw
Avatar
出したばかりのプロポーザルでβ版出しちゃうのはおいマジかwって感じではある
Avatar
omochimetaru 6/4/2019 4:57 AM
まあコアチームは決定権もってるからね
4:57 AM
evolプロセスは民主的だけど平等ではないw
Avatar
Property Wrapper(Property Delegate)もまだApprovedではないけど使われてるし (edited)
Avatar
omochimetaru 6/4/2019 4:58 AM
でもこれは受け入れられるだろうし、バランス感覚を感じる
Avatar
なるほどう。ありがとうございます!まだ理解半分ですが酔いが冷めたら改めてみます。それにしてもいきなり尖ったの入ってきた感があるな。。楽しくなってきた
Avatar
omochimetaru 6/4/2019 5:00 AM
情報量が多すぎるよねw
Avatar
本当にw
Avatar
omochimetaru 6/4/2019 5:12 AM
あーそうか
5:12 AM
だからこれって、ReactのJSXだ
5:12 AM
JSXはJSにそのままXMLを埋め込んで言語拡張したけど
5:13 AM
このアプローチは、ホスト言語のSwift自体にDSLサポートを追加することで
5:13 AM
ゲスト言語のビュー構造の記述をSwiftのままやってるんだわ
Avatar
そしてそれはGoogleが今年のI/Oで発表したJetpack Composeと同じ流れで、こちらもKotlinオンリー https://qiita.com/shyne/items/d149f4ccde308d7019af

はじめに

 Google I/O 2019で発表されたJetpack Composeについて調査しました。本記事ではその中でも特徴的な宣言的UIの構築を行うための文法についてまとめてみます。

注意点

本記事は5/2...
🤔 1
Avatar
yutailang0119 6/4/2019 5:58 AM
ここまでの話をikesyoからリモートで聴いてる
Avatar
omochimetaru 6/4/2019 6:15 AM
あれ?yutaroが現地でikesyoが京都?w
6:15 AM
ややこしい仕入れだ
Avatar
Jetpack Composeもアノテーションプロセッシングによるコード生成でDSLを実現してますね http://intelligiblebabble.com/compose-from-first-principles/ (edited)
Thousands of Developers from around the world attended Google I/O 2019 earlier this month. It was a particularly exciting I/O for me, as it…
Avatar
omochimetaru 6/4/2019 6:23 AM
このシンクロはなんなんだろう
6:24 AM
裏の談合があるのか、それとも、たまたま同時代に同種のブレークスルーが複数発生する現象のアレなのかな (edited)
Avatar
omochimetaru 6/4/2019 6:47 AM
考えてみたら、ORTのproposalで、
6:47 AM
GameObjectをtransformするなどのオペレーションの適用結果をGenericsで維持すると、
6:47 AM
どんどんややこしい型になるんだよ、ってストーリーが提示されてたけど
6:47 AM
あれは完全にSwiftUIの事だよなあ
6:48 AM
悟られないように2D View Systemの例は出さなかったに違いない
Avatar
伏線回収が鮮やかすぎる😎
Avatar
norio_nomura 6/5/2019 8:21 AM
Building a scrubbable UI timeline is really straightforward – I can't wait to see the tooling that evolves around SwiftUI. https://t.co/CDQUPa0yu5
Likes
190
👍 1
Avatar
Viewが値型だからこそ可能な世界観ですね
Avatar
omochimetaru 6/5/2019 8:37 AM
文字入力の過程もbindingされてるのか。日本語入力は大丈夫かな。
Avatar
norio_nomura 6/6/2019 12:31 AM
An experimental time traveling state store for SwiftUI - timdonnelly/SwiftUITimeTravel
Avatar
omochimetaru 6/6/2019 10:21 AM
AutoLayoutはなくなってるんかな、SafeAreaとの絡みを書く方法はあるんだろうか?
Avatar
ScrollView入れたらSafeAreaの中に入る予感がする
10:22 AM
SwiftUI、多分ちょっとでも難しいことしようとしたらUIKitにコンポーネント書かないといけないと思います
10:22 AM
あと個人的にはWebViewがどうしてもうまくいかないと思うのだけどどうなるんだろう、と。
Avatar
edgesIgnoringSafeArea IgnoringのためのAPIがあるということは、ScrollViewとか関係なく、全てViewは標準だとSafeAreaの中にレンダされるのかな
Avatar
omochimetaru 6/6/2019 10:33 AM
ふむふむ
10:34 AM
標準のNavigationBarを使わずに自作する時とか
Avatar
チュートリアルでは、 List でも普通にやったら SafeArea 考慮されてて、 edgesIgnoringSafeArea で SafeArea 無視されてましたね。
Avatar
omochimetaru 6/6/2019 10:34 AM
よく、SafeAreaのTopから49ptまでバーを伸ばす、とかやるけど
10:37 AM
ignoreできるだけだと厳しそうだな?
Avatar
複雑な AutoLayout を再現できるのかよくわかってない。今知ってる知識だけだとできなさそうだけど、 API 調べてみないと何とも。
10:50 AM
このあたりでかなりコントロールできる?
Avatar
SwiftUI のメソッドチェーンでインスタンス作り上げていく API 、あまり Swifty でない気がするけど宣言的に書くためには仕方ないのかなぁ。
Avatar
omochimetaru 6/6/2019 12:36 PM
値型だから毎回新しいインスタンスなのと、変換を型に焼きこんでるから、
12:36 PM
いわゆるselfを返すメソッドチェーンとは意味が違う気がしています
12:37 PM
というか、selfは返ってないから、 mapしてfilterして、、みたいな
12:37 PM
関数型データフローっぽい
Avatar
うん、そういう API があんまり標準ライブラリでは見られない気がして、 Swift っぽくない感じがしてる。
Avatar
omochimetaru 6/6/2019 12:39 PM
LazyMapぐらいですかね
12:40 PM
型がゴテゴテになっちゃうから、
12:40 PM
ORTが無い時はきつかったから
12:40 PM
言語が進化して採用可能になるパターンという理解
12:41 PM
必然的にこれまでのSwiftでは見られないやり方
Avatar
今まではそういう部分でも型消し使ってたのかな
12:42 PM
そうするとORTがパフォーマンスに与える影響はかなり大きそう
Avatar
omochimetaru 6/6/2019 12:43 PM
AnyPに逃してたはず
Avatar
https://developer.apple.com/documentation/swiftui/text/3276841-bold とか、↓的な考え方では値型のプロパティを不変にする意味はないから、新規インスタンスを生成するよりも当該プロパティを書き換えられるのが自然な気がするんだけど。型が変わってしまうものとの整合性を考えるとメソッドチェーンでインスタンス作ってくしかないのかな・・・。 https://qiita.com/omochimetaru/items/7265e440418b38088ccb

記事について

この記事は potatotips#39 という勉強会で発表した内容を再掲したものです。スライドの画像と、喋った言葉を載...
Avatar
omochimetaru 6/6/2019 12:46 PM
全く型が変わらないオペレータもあるのか。 これは従来だと bolded になるはずですね
12:48 PM
型が変わるやつとの整合性、っぽいですねえ
12:48 PM
functionBuilderとの相性もあるだろうけど、そこはニワトリタマゴだし。
12:49 PM
本当は、mutating funcでvarの型が変わるのが良かったのかも
12:49 PM
shadowingとして筋は通せる
Avatar
前にも話してたけど、イミュータブルオブジェクトを生成して状態変更していくパターンに対して値型のプロパティを変更するのは、型が変更できないデメリットがあるよね。
Avatar
omochimetaru 6/6/2019 12:50 PM
そういうことになりますね。
12:50 PM
inout考えると型変わるのはダメか。
12:50 PM
たしかに歪な気もしてきた
12:51 PM
SwiftUIをDSL風にすることが最大限に優先されてて、他はちょっと歪んでる
Avatar
shadowingとして筋は通せる
Rust 思い出した。↓とか。 fn main() { let x = 5; let x = x + 1; let x = x * 2; println!("The value of x is: {}", x); } https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
Avatar
omochimetaru 6/6/2019 12:55 PM
Rustはそれアリなのか〜
Avatar
mut let のときと let でシャドーイングのときを区別しないといけなくて、 Swift 的思考だとハマりそう。 (edited)
Avatar
omochimetaru 6/6/2019 12:56 PM
mut letの再代入のときはletの無いただの代入文だから見た目にすぐわかるのでは?
Avatar
ああ、見た目の区別というか、概念としてまったく異なるけどどっちも変更しているように見えてしまうというか。
Avatar
omochimetaru 6/6/2019 12:58 PM
Swiftでも、引数とローカル変数とか
12:58 PM
letでshadowできる場面もあるから
12:58 PM
それの仲間に見えるんじゃないか
Avatar
うーん、そうかも?でも↑のコード見たときはびっくりしたなぁ。
Avatar
↓とかできるの面白いな。これ(二重の padding )はインスタンス生成してないとできない。 Text(string) .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) .background(Color.white) .cornerRadius(16) .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10))
Avatar
padding重ね、これ面白かったですね https://twitter.com/twostraws/status/1136130799600668672
SwiftUI, folks. #WWDC19 #Pride
Retweets
154
Likes
802
👍 2
Avatar
これわかりやすいですね🙂 (edited)
1:42 PM
ORT の恩恵もわかりやすいですね。 https://twitter.com/Pahitos/status/1136468991051018240
@twostraws And this is the type of that view… What’s the benefit of relying so heavily on the type system to represent those views? cc @jckarter
Avatar
@omochimetaru 昨日話してたカスタム NavBar 、とりあえず↓みたいに GeometryReader 使ってできそう。もっといいやり方ありそうだけど。 struct ContentView : View { var body: some View { VStack { GeometryReader { geometry in VStack { Spacer() .frame( width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.safeAreaInsets.top ) .background(Color.blue) ZStack { Spacer() .frame( width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: 100 ) .background(Color.red) Text("Custom Bar") } } .edgesIgnoringSafeArea(.init([ .top, .leading, .trailing ])) } } } } (edited)
Avatar
omochimetaru 6/7/2019 1:05 AM
おお、そうやって自分で計算書けるんですね それなら大抵のケースは最悪どうにでもできるな
Avatar
layoutPriority とかもあるみたいだから、かなりのことはできそう。 https://developer.apple.com/documentation/swiftui/view/3278584-layoutpriority
Avatar
omochimetaru 6/7/2019 1:09 AM
ふむふむ
Avatar
どこまでできてどこからできないのかわからないから、 NDA 解けたら、 Auto Layout のこれを SwiftUI でできますか?コンテストとかやったらおもしろいかもね。
1:13 AM
階層を超えた centerX 合わせとかきつそう。
Avatar
GeometryReader 胸熱
1:53 PM
そして struct ContentView : View { @State var int = 0 var body: some View { let red = int > 0 Text("Hello World") .background(red ? Color.red : Color.blue) } } ↑はビルドエラーになるけど struct ContentView : View { @State var int = 0 var red: Bool { int > 0 } var body: some View { Text("Hello World") .background(red ? Color.red : Color.blue) } } ならOKですね
Avatar
SwiftUI作ってる人のアカウントをみつけた https://twitter.com/ricketson_
SwiftUI @ 
Tweets
2725
Followers
607
Avatar
Kishikawa Katsumi 6/7/2019 5:12 PM
5:13 PM
SwiftUIにMultiline Literalを与えるとプレビューが混乱する。 うまくいくときもある。 これはなんか難しいことをしてそう。
5:14 PM
消して再入力してやり直すとうまくいったりする
Avatar
↑と同じネタで、Listの中のTextの高さが上手くいかない、という報告もあがってましたね。
Avatar
Kishikawa Katsumi 6/7/2019 5:40 PM
バグレポートはいっぱいきてて大変そう。どんどん送らないと。
5:43 PM
Image("owl") .resizable() .edgesIgnoringSafeArea([.top, .horizontal]) ^ これを Image("owl") .edgesIgnoringSafeArea([.top, .horizontal]) .resizable() ^ このように入れ替えられない、とかけっこう難しいと思うんですよね。
Avatar
これは「かなり厳しいGenericsだ」みたいにKnownIssueとしてつけられてたので、何かしら治るんじゃないかなと予感しています (edited)
Avatar
Kishikawa Katsumi 6/7/2019 5:45 PM
そうなんですね。直ってほしい。
Avatar
Kishikawa Katsumi 6/7/2019 6:54 PM
インスペクタが大変なことになった。
Avatar
こういうセンターラインのずれとか、どうやって補正すればいいんだろう
10:17 PM
あとこのスターはボタンなのだけど、destinationへの遷移が優先されてしまう
Avatar
WWDCでSwiftUIが発表されてから数日が経ちました。一気に世界が変わった気がしますね。 ただ、UIKitと同様にSwiftUIはオープンソースでは無いため、我々開発者は依然挙動をエスパーしながら開発する必要があ...
👍 1
Avatar
今更ながら @State なpropertyが中身のオブジェクトのアクセサ持ってるの、これ使ってるんですね https://twitter.com/v_pradeilles/status/1136755695427018752?s=21
dynamicMemberLookup now supports typed KeyPaths 🤯 This means a type can now seamlessly integrate the API of its dependency within its own API ⬇️
Retweets
105
Likes
369
3:05 PM
色々悪いこと出来そう
Avatar
Kishikawa Katsumi 6/10/2019 9:19 PM
https://twitter.com/terhechte/status/1138106114304819201 セル内のボタンがタップになるのは他の人も困ってるみたい
Did anybody figure out how to create a List row in #SwiftUI that forwards the tap events to controls in the cell? (i.e. List { VStack { Text(..) Button(action: ..) { Text("Press me") } } }` Trying to tap the button just selects the cell
Avatar
dynamicがPure Swiftのメソッド・プロパティにも付けられるようになってて、_@dynamicReplacememtで差し替えられる機構がホットリロードに使われてるようで面白い! https://forums.swift.org/t/how-does-the-hot-reloading-work-in-xcode11/25312 https://forums.swift.org/t/dynamic-method-replacement/16619
I'm interested in the hot-reloading demonstrated for SwiftUI. How is this implemented? Could something similar be achieved for a desktop application using the swift toolchain independently of XCode?
Pitch: Dynamic method replacement Introduction Objective-C allows swapping method implementations "swizzling" e.g. via the method_setImplemenation API. We can make use of this facility in Swift by marking methods with @objc dynamic. class Thing : NSObject { @objc dynamic ...
11:51 PM
The Swift Programming Language. Contribute to apple/swift development by creating an account on GitHub.
11:53 PM
5.0 runtimeへのバックデプロイもやろうとしてる https://github.com/apple/swift/pull/25340
This will allow backward deployment to a swift 5.0 runtime. rdar://problem/51601233
Avatar
おお
Avatar
おお
Avatar
https://github.com/apple/swift/pull/20333 ここが実装スタート地点か
Implement dynamic function replacement as described in https://forums.swift.org/t/dynamic-method-replacement. Allow dynamic on non-@objc classes, struct, and enum functions, properties, initializer...
Avatar
置き換えられるのは、あらかじめdynamicと宣言したモノだけか。
Avatar
あんま興味なかったんだけどhot reloadに使ってると聞いて興味が出てきた
12:51 AM
単純にクロージャ型の変数にしてるんだろうか?
Avatar
ORTが実装されるときにORTのdynamic replacementもガシガシ実装されてたけど
12:59 AM
型が同じじゃないといけない?けどORTなら消えてるからView.viewの置き換えができるんかな
Avatar
SwiftUIでリアルタイムプレビューする方法がわからないのですが、どなたか教えていただけないでしょうか?
Avatar
Appleのサンプルを見ると良いですよ。 なおOSもβ版入れないと、表示されないので注意が必要です。
Avatar
ああ、、そうでしたか....
8:19 AM
了解です
8:19 AM
ありがとうございます
Avatar
無事使えるようになりました!!
12:23 AM
ありがとうございます
Avatar
norio_nomura 7/1/2019 2:32 AM
A small toy implementation of #SwiftUI for the Web: https://t.co/UjxV4AodzQ
Likes
167
Avatar
CGDrawingViewがLabelかButtonかなどのメタ情報の取り方を知ってる方いますか?色々試したんですが無さそうでして
Avatar
omochimetaru 7/24/2019 9:18 AM
ネイティブ側のビュー(CGDrawingViewもその一つ)にSwiftUIのビュー構造は保持されないので、取れないと思います
Avatar
ですよねー、SwiftSyntaxでコードを喰わせればなんだったを保持して置くことは出来そうな気もしてたんですが
Avatar
yutailang0119 8/20/2019 3:58 AM
Xcode 11 beta 6で Binding<Value>.value -> wrappedValue に変更されているみたいだけど、Docに反映されていなそう? https://developer.apple.com/documentation/swiftui/binding?changes=latest_beta (edited)
4:00 AM
Doc diffにも出てこないし、こんなことあるんだ
Avatar
norio_nomura 8/20/2019 7:20 AM
beta 5とbeta 6でSwiftUI.framework/Versions/A/Modules/SwiftUI.swiftmodule/x86_64.swiftinterfaceを比較しても、その辺り変わっていない様な。
Avatar
yutailang0119 8/20/2019 8:47 AM
おや...なんでbeta 6にしたらエラーになっているのだろう...
Avatar
SwiftUIってiOS13以降じゃないとダメなんですね... 既存のプロジェクトにも適用できるのかなと思ってました
Avatar
「Static Framework が含まれる環境で SwiftUI のプレビューを動作させるには Linker flags に -fprofile-instr-generate を追加する必要がある」この原理を考察してみました(多分間違ってる)。 考察に納得できてないので、もし知ってることがあれば教えていただきたいです!(この窓で良いのかな…) https://qiita.com/AkkeyLab/items/a698545f2423b9b1dec9

はじめに

この記事は CyberAgent Developers Advent Calendar 2019 9日目の記事になります。 今回は、コンパイラ...
Avatar
記事は見たんですけど、特に違和感無かったので、問題ないと思いますが、 どのへんが疑問として残っていますか?
3:49 AM
(想像は付いているんですが、すれ違いがあると余計に混乱させてしまいそうなので)
Avatar
「フラグを追加すると Static Framework を参照可能になるのはなぜなのか」というところがまだ理解できていない感じになります。
Avatar
Kishikawa Katsumi 12/18/2019 4:13 AM
確認なんですけど、 問題を解決する方法は2つで、 a) アプリのプロジェクト側でCode CoverageのオプションをOFFにする(かつ-fprofile-instr-generateなし) b) Code CoverageのオプションはONかつLinker Flagに-fprofile-instr-generateを渡す のどちらかなんですよね? (edited)
Avatar
はい、その2つの挙動を確認しています。
Avatar
フラグを追加すると Static Framework を参照可能になるのはなぜなのか
カバレッジ計測を有効にしてコンパイルすると、通常のコードに加えて、 カバレッジランタイムライブラリの呼び出しが追加されたコードが生成されます。 カバレッジランタイムライブラリは、実行ファイルをビルドするときに、事前に用意されたものを、リンカーが結合することで有効化されます。 Static Frameworkの中にカバレッジランタイムライブラリの呼び出しが含まれているので、オプションを与えていないと、リンカーがライブラリを提供しないので、呼び出そうとしているライブラリ関数が見つからず、undefined symbolエラーが出てしまいます。
(edited)
👀 1
Avatar
Kishikawa Katsumi 12/18/2019 4:21 AM
Static Frameworkの中にカバレッジランタイムライブラリの呼び出しが含まれているので
気になってるんだけど、Code Coverage ONでビルドされて配布されてるのかな?
Avatar
As for needing -fprofile-instr-generate, IIRC we disabled code coverage because of App Store submission issue. Though apparently Carthage 0.26 has formally addressed the issue. Perhaps we can now reenable it.
4:29 AM
I&#39;ve been scratching my head all day as to why our new Carthage static libs setup works great for all our dependencies except Reactive(ObjC|Swift|Cocoa). For these frameworks, when I try to...
4:29 AM
記事から参照されている、ReactiveSwiftの事例に関しては、有効にして配布していたけどやっぱやめた、って話のようですね。
4:34 AM
$ lipo -extract x86_64 FirebaseCore -output FirebaseCoreX64 $ nm -a FirebaseCoreX64 | grep -i llvm ダウンロードしたFirebaseCoreには含まれてなさそう。
Avatar
Kishikawa Katsumi 12/18/2019 4:35 AM
そうなんだよねえ。私も環境作って試してるんですけど。
Avatar
cocoa podだと設定されるんじゃないですか?(調査中
Avatar
参考になるかわかりませんが FirebaseCore が見当たらないと言われたアプリでは Firebase/Analytics を pods で入れてます。
Avatar
Kishikawa Katsumi 12/18/2019 4:39 AM
なるほど。ちなみに厳密にはFirebaseCoreが見つからないんじゃなくて、FirbaseCoreが呼び出そうとしているカバレッジ計測のランタイムライブラリが見つからないというエラーです。 なのでリンカフラグを付けてカバレッジ計測のランタイムライブラリがリンクされるようにするとエラーが消える。
👀 1
4:40 AM
Static Frameworkということだから、ビルド済みのものを考えていたけど、
4:40 AM
CoocaPodsによってビルドされるStatic Frameworkか。
Avatar
そうですね、referenced from: ___llvm_profile_runtime_user in FirebaseCore なので、英文的には、見つからなかったのはFirebaseCoreじゃなくて、 FirebaseCoreの中から 参照している llvm_profile_runtime_user が、見つからなかった、ですね。
👀 1
Avatar
Kishikawa Katsumi 12/18/2019 4:41 AM
で、プロジェクトでカバレッジを有効にしているとPodsの方のビルドもカバレッジが有効になって、コードカバレッジが有効なStatic Frameworkが生成されている。
4:43 AM
で、SwiftUIのプレビューに使われるバイナリはスキーマの設定(コードカバレッジの設定)のいかんにかかわらず、カバレッジなしのバイナリになってるから、リンクできない、かな? (edited)
Avatar
あー、そうっすね、「カバレッジを有効にする」っていう部分の操作が、
4:44 AM
CocoaPods流のxcworkspaceでの環境においてどういうオペレーションなのかにもよるのか。
Avatar
Kishikawa Katsumi 12/18/2019 4:44 AM
^ で書いたのはたぶん合ってる気がする。
Avatar
podsでAnalyticsを入れるところまで追いついたけどこの先がわからない。。
Avatar
Kishikawa Katsumi 12/18/2019 4:46 AM
ちなみに、-fprofile-instr-generateで解決するのはそのまま出荷されてしまうとあまり嬉しくないのでカバレッジをOFFにする方がいいと思いますよ。 (スキーマでON/OFFするやつはこの問題がなかったとしても使わない方が良くて、必要な時にコマンドラインから渡す、方がいいです)
Avatar
実際にカバレッジを無効にしてみると tapple でも AkkeyTV でも Xcode Preview が正常に動作するようになりました!しかし、テストを書きながら開発を進めているため、この解決方法はボツにしました。
僕もここがよくわからなくて、
4:48 AM
テストするときはカバレッジ有効になってて、
4:48 AM
普段のデバッグ実行はカバレッジ無効になってる、 って状態を目指した方が良いと思う。
4:48 AM
普段LeaksとかTime Profileするときはprofileビルドはまた別でビルドキャッシュが効いてる感じだしできるはずだと思うんだけど
4:49 AM
CocoaPodsでできるのかどうかとかは知らないです
Avatar
Kishikawa Katsumi 12/18/2019 4:49 AM
Carthageでも問題になったけどあのスキーマにある設定を使うと基本的にビルドするのが全部Instruments付きになってしまう。アーカイブの時だけはならないから大体問題ないようになってるけど。。。 (edited)
Avatar
あのスキーマの設定ってなんですか? Run, Test, Profileで別々に設定があって、 Testの Gather coverateのチェックボックスの事?
Avatar
Kishikawa Katsumi 12/18/2019 4:51 AM
そうそれ > Gather coverate
Avatar
edit scheme > test > code coverage ここのチェックを外すことを「カバレッジを無効にする」と表現してました。
Avatar
うお、ほんとうだ
4:55 AM
そうかこれ、Product CacheはRun/Test/Profileではわかれてなくて、
4:55 AM
Debug/Releaseでしか区別が無い・・・?
4:55 AM
で、たしかに、Testスキームでカバレッジを有効にすると、Runスキームのビルドも有効になるんですね。
Avatar
ちなみに、-fprofile-instr-generateで解決するのはそのまま出荷されてしまうとあまり嬉しくないのでカバレッジをOFFにする方がいいと思いますよ。
出荷というのは、ストアにアップロードするという認識で合ってますか?ここらへん調べてるときに、カバレッジが有効な状態のアプリをアップロードしようとするとエラーで弾かれるという投稿を見かけたので問題ないだろうと判断してました、、
Avatar
Kishikawa Katsumi 12/18/2019 4:58 AM
まあだいたい問題ないんですけど、
Avatar
へえ、AppStore親切だ。 > カバレッジが有効な状態のアプリをアップロードしようとするとエラーで弾かれる
4:58 AM
この構成ならアーカイブビルドのときもPods/Firebaseが専用にビルドされるから、大丈夫そうですね。
4:59 AM
Carthageで事前ビルドしたフレームワークとかだと怪しそう・・・
Avatar
Kishikawa Katsumi 12/18/2019 4:59 AM
出荷されてしまうとっていうのは正確じゃなかった。リリースビルドがカバレッジ有効になっちゃう恐れがあって、例えば申請でエラーになるとか今回みたいなリンクの問題とかが起こるんで、
5:00 AM
使わない方がいい、ですね。 (edited)
5:01 AM
ほとんどの人は毎回手元でユニットテストのカバレッジとらないし、CIだけで有効になってる方がいいはず。
Avatar
そうすると理想の解決策は、
5:02 AM
SwiftUIの内部ビルドに
5:02 AM
カバレッジ有効フラグを適切に注入する事ですけど
5:02 AM
多分そんな設定できる場所無いよな。
Avatar
Kishikawa Katsumi 12/18/2019 5:03 AM
リンクエラーまでは再現した。
5:03 AM
@omochimetaru プロジェクト共有しましょうか?
Avatar
お、ほしいです
Avatar
ほとんどの人は毎回手元でユニットテストのカバレッジとらないし、CIだけで有効になってる方がいいはず。
納得です! Xcode でデフォルト OFF にしてほしいなぁと思っちゃいました
5:07 AM
Code CoverageのチェックはデフォルトOFFじゃなかったかな?
Avatar
あ、かもしれないですね、、
Avatar
Kishikawa Katsumi 12/18/2019 5:08 AM
@omochimetaru DerivedData 消した後でも再現したから再現すると思う。 カバレッジ有効になってるのでContentViewのプレビューをしようとしたらリンカエラーが見えると思います。カバレッジOFFでプレビューできる。
5:09 AM
CocoaPodsで最低限必要なFirebaseのインストールをして、念のためFirebaseのimportと呼び出しのコードを書いて、コードカバレッジをONにする、という手順で作りました。
Avatar
お、再現できました。
5:14 AM
Previewしようとするとビルドログができて、
Avatar
Kishikawa Katsumi 12/18/2019 5:14 AM
まあこれはわかりやすいのでバグレポートでいいんじゃないかな? 期待する挙動としては a) 問題なくプレビューできる b) エラーメッセージをわかりやすくする のどちらかじゃない?
👀 1
Avatar
そっちはアプリ自体はビルドできているんですね。
Avatar
Kishikawa Katsumi 12/18/2019 5:14 AM
アプリもビルドできないって話でしたっけ
Avatar
あいや、内部的な話で
5:15 AM
SwiftUI Previewが内部でアプリをビルドした上で、
5:15 AM
さらにContentView.swiftのプレビュー用になにかしようとしていて、
5:15 AM
その最後のフェーズのリンクで落ちてるっぽいなあということが見えた。 (edited)
Avatar
プレビューが表示されてる裏(見えないけど)でシミュレータみたいな感じで立ち上がるやつですよね?
5:18 AM
動画を起動後自動再生するアプリでプレビューさせようとすると音だけ聞こえてくるので気が付きました。
Avatar
そうです、音なるのかw
5:19 AM
裏でContentView.swiftをソース書き換えしたりしてます。
Avatar
そこらへんがどんな仕組みなのか気になってこの記事読んでみたりもしました。 https://tech.guardsquare.com/posts/behind-swift-ui-previews/
Recently, Apple introduced ‘SwiftUI’, a new framework for building native UIs across all Apple’s platforms. The core selling point of the framework is that it allows defining application interfaces in a declarative way. SwiftUI is expected to significantly simplify the...
Avatar
あ、そうそう、これ。 > TextView.3.preview-thunk.swift
5:26 AM
状況としては、SwiftUI Preview用の内部ビルドが、XcodeのCode coverageの設定にうまく追従できてないっていう不具合というか機能不足だと思います。
👀 1
Avatar
Kishikawa Katsumi 12/18/2019 5:28 AM
リンカーフラグは見るけどスキーマにあるCode coverageの設定は無視している、っていうのがおかしい、ということでそうですね。 私はスキーマのCode coverageのチェックボックスを直した方がいいと思うけど。。。😛
Avatar
SwiftUI Preview の内部ビルドに対しても xcspec みたいな存在があるのか気になりますね。あるとするなら、そこに Code coverage の設定を見る処理が不足しているってことになりそうですね。
5:37 AM
すごく勉強になりました! ありがとうございます!!
Avatar
全く手元で試してないので妄言かもしれないですが、Preview用のターゲットを作ると解決できないですかね?
Avatar
あ〜そっか。
5:42 AM
Code Coverageの設定が別にあればいいもんね。
Avatar
Preview用のターゲットではカバレッジをオフにして、本体はオンにする
5:44 AM
というかこの場合はbuild configurationを弄る必要はなくて、カバレッジ設定をターゲットを切り替えるだけでいいからターゲットを作るまでもなくpreview用のスキーマを作れば事足りそう
Avatar
同じターゲットのスキーマ違いか!
5:46 AM
需要初めて見た
5:46 AM
あれ、それプロダクトキャッシュちゃんと分離されるのか?
Avatar
Kishikawa Katsumi 1/6/2020 12:11 AM
@swiftbot import SwiftUI struct ContentView: View { var message: String @State var isOn = true var body: some View { VStack { VStack { Toggle(isOn: $isOn) { Text("Switch") .font(.title) .foregroundColor(Color.white) } } .padding() .background(isOn ? Color.purple : Color.orange) Text(message) .font(.largeTitle) .foregroundColor(.blue) } } } struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView(message: "Hello SwiftUI Playground!") } }
Avatar
Kishikawa Katsumi 1/6/2020 12:13 AM
MacStadiumのマシンで作ってみた。
Avatar
omochimetaru 1/6/2020 12:14 AM
うおおおおおお
Avatar
Kishikawa Katsumi 1/6/2020 12:14 AM
PreviewのModifierとかこれからサポートする予定。今はまだ固定サイズでしか出ない。
12:14 AM
よかったら適度にテストしてほしい 🙏
Avatar
omochimetaru 1/6/2020 12:15 AM
コマンドライン出力すっとばして先にSwiftUIなんですね
Avatar
Kishikawa Katsumi 1/6/2020 12:15 AM
脆弱性とか見つけたら速やかにDMで教えてください。
12:15 AM
まあ、macOSの環境で動くやつ作ってもよかったけど、それはすぐできるし、あんまりやる気にならなかった。
Avatar
omochimetaru 1/6/2020 12:16 AM
やる気なるほど
Avatar
Kishikawa Katsumi 1/6/2020 12:18 AM
あとはOSをスイッチするためのフラグがいい感じのが思いついてなくて。
Avatar
omochimetaru 1/6/2020 12:22 AM
あ、同じbotなのか。
12:22 AM
import SwiftUIで切り替えてるんですか?
Avatar
Kishikawa Katsumi 1/6/2020 12:23 AM
大正解!さすがですね 😄
🍎 1
12:24 AM
同じじゃなくてもいいんですけど、同じ方が簡単だったから。
Avatar
Kishikawa Katsumi 1/6/2020 12:53 AM
@swiftbot import SwiftUI struct ContentView: View { var body: some View { VStack { // 1. Rectangle() .stroke(Color.blue, lineWidth: 10) .frame(width: 100, height: 100) // 2. Circle() .fill(Color.red) .frame(width: 100, height: 100) // 3. Capsule() .fill(Color.green) .overlay( Capsule() .stroke(Color.black, lineWidth: 10) ) .frame(width: 200, height: 100) // 4. RoundedRectangle(cornerRadius: 20) .fill(Color.yellow) .frame(width: 100, height: 100) } } } struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView() } }
Avatar
すげえ!
Avatar
Kishikawa Katsumi 1/6/2020 12:57 AM
すごいでしょ 😄
12:58 AM
今夜か明日にはコード公開するので納得いってないところアドバイスしてほしい。
👀 3
Avatar
すごい
Avatar
Kishikawa Katsumi 1/6/2020 2:00 PM
ありがとう 😄
swift 2
Avatar
すごい
Avatar
Kishikawa Katsumi 1/6/2020 3:10 PM
どうも。Webの方もなんとなく動くようになりました。 https://swiftui-playground.kishikawakatsumi.com/ 実力不足によりWebを書き始めるととたんに開発スピードが落ちる。。。
🐌 1
Avatar
中華フォント問題は深刻。技術的な文化のinvasionだと思ってる。
Avatar
Kishikawa Katsumi 1/6/2020 4:24 PM
うーむ。
Avatar
en_US (Default)だとほとんどのシステムで zh_Hans が ja_JP より高いので...
4:28 PM
(UnicodeがCJK包摂漢字なんてことしたのが全ての原因)
Avatar
Kishikawa Katsumi 1/6/2020 4:29 PM
CJK包摂漢字が元凶なのはそうですね。包摂していいものとダメなものが理解されてなかった。
4:36 PM
struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, .init(identifier: "ja")) } } とすると手元ではOKだけどサーバーでは変わらない。 日本語にフォールバックされる要件が相変わらずよくわからない。
Avatar
Kishikawa Katsumi 1/6/2020 4:50 PM
あ、いや .environment(\.locale, .init(identifier: "ja")) 関係なかった。
4:55 PM
単純にシミュレータのPreffered Language Orderに日本語があるかどうかか。
Avatar
だと思いますが
5:02 PM
アプリでランタイムで上書きできるはず。
Avatar
Kishikawa Katsumi 1/6/2020 9:35 PM
https://github.com/kishikawakatsumi/swiftui-playground ^ Playgroundのコード公開しました。
SwiftUI Online Playground. Contribute to kishikawakatsumi/swiftui-playground development by creating an account on GitHub.
9:38 PM
Previewを出力するためにまず、 https://github.com/kishikawakatsumi/swiftui-playground/blob/master/PreviewProvider/Sources/PreviewProviderParser/main.swift ^ SwiftSyntaxで入力されたコードからPreviewProviderに準拠しているものを探します。 これは自由入力されたコードから探さないといけないので、方法はいくつかあるけど必要なプロセスだと思う。
9:42 PM
で、PreviewProviderはGroupとか複数画面サイズとかできるのでそれに対応するために(ちゃんと対応できてないけど。。。)一回ビルド、実行してPreviewProvider.previewsをMirrorを使ってリフレクションで構造を読み取ります。 modifierの指定を集めるのと、Groupを探してGroupがあったらXcodeの挙動と一致するように分割します。
9:45 PM
で、そのあとはUIHostingControllerに入れて画像として出力するのですが、その際に some View の方がわからないと渡せないので上記のリフレクションで構造を読む際に実際の型名を書き込んだソースコードを生成して、再度ビルドして実行します。 2回目の実行で画像が出力されます。
9:49 PM
改善したいところは2つあって、1回目のリフレクションで構造を読むところ、そもそもあまり正しく動いてないし、コードもいきあたりばったりで書いたやつだから美しくしたい。
9:52 PM
もう一つは、そもそも2回ビルドして実行しているのを可能ならやめたい。。。 実際の型を書き込むために1回目でリフレクションとコード生成、2回目で実行、としているのでこれはもっとスマートな方法があったら解決する気がします。 妥協したくないポイントは、PreviewをGroupやサイズなどで複数出力できるというところです。
9:52 PM
それを守りつつ、より良い方法を探したい。
Avatar
試してないんで分からないですがAnyView使って型消去じゃダメですか?
Avatar
Kishikawa Katsumi 1/7/2020 1:12 AM
Mirrorから得たものはAnyになっちゃうのでAnyViewにもできないのです😢
Avatar
なるほど
Avatar
ちょうど #swiftbot-sandbox で話題になってるAnyEquatableの手法を応用すると、もしかしたらですがMirror無しに動かせるかもしれないです
1:26 AM
GroupとModifedContentはstructなので、これに後付けでAnyViewを吐き出すextensionを生やしてあげればいいのかな
Avatar
Kishikawa Katsumi 1/7/2020 1:43 AM
あ、言われてなんかひらめいた気がします。ちょっとやってみます。
Avatar
Kishikawa Katsumi 1/7/2020 2:21 AM
意外とMac miniのスペックでもがんばってる気がする。 だんだん重くなっていくような挙動をしてたけど、原因は画像アップロード先のRate Limitで画像を表示できなくなってるだけだった。とりあえず直接配信するようにして様子を見る。
Avatar
キャッシュしてます?
3:07 AM
多分Runボタンいきなり押す人おおいとおもうので
3:07 AM
同じリクエスならキャッシュから読むとかしないとしんどい
Avatar
Kishikawa Katsumi 1/7/2020 3:08 AM
実は何もしてないです。
3:08 AM
今夜あたりその辺に手をつけてみます。
3:11 AM
ただ、実行はしないといけないんで、シンプルにならなさそうで困っています。 例えば Text("\(Date())") みたいなコードがあったら文字列としては一致してるけど結果は変わっちゃうとか。
Avatar
真面目にやるならあれですが、この最初にはいってるデフォルトのコードの場合だけでもcacheすれば相当 (edited)
3:40 AM
トラフィック減りそう
3:40 AM
logとかみてみないと分かりませんが。
Avatar
Kishikawa Katsumi 1/7/2020 3:46 AM
実行されてるのはほとんどデフォルトのコードなんで、それは大胆にキャッシュして良さそうです。
Avatar
@swiftbot import SwiftUI extension View { func when<NewView: View>(_ cond: Bool, @ViewBuilder then apply: (Self) -> NewView) -> some View { Group { if cond { apply(self) } else { self } } } } struct MyView: View { var body: some View { Text("a") // .when(true) { // $0 // } // .when(Bool.random()) { // $0.padding(10).background(Color.yellow) // } // .when(Bool.random()) { // $0.padding(10).background(Color.green) // } .when(true) { view in VStack { Text("begin") view Text("end") } } } } struct MyView_Preview: PreviewProvider { static var previews: some View { MyView() } }
Avatar
swiftbot BOT 1/22/2020 7:28 AM
Avatar
@swiftbot import SwiftUI extension View { func when<NewView: View>(_ cond: Bool, @ViewBuilder then apply: (Self) -> NewView) -> some View { Group { if cond { apply(self) } else { self } } } } struct MyView: View { var body: some View { Text("a") .when(true) { $0 } .when(true) { view in VStack { Text("begin") view Text("end") } } } } struct MyView_Preview: PreviewProvider { static var previews: some View { MyView() } }
Avatar
swiftbot BOT 1/22/2020 7:28 AM
22:17: error: cannot convert value of type 'VStack<Content>' to closure result type '_' VStack { ^~~~~~~~
Avatar
多分ここでいいんだと思うんだけど
1:04 PM
Minimum reproducible code. It may be fixed on 11.4 but similar issue is in SwiftUI, which are not fixed on at least 11.4b2. https://t.co/aV1bRx0CvM
1:04 PM
この例は Combine だけど、これ地味にしんどい
1:05 PM
SwiftUIは11.4b2でもweakにならない
1:05 PM
明示的にframeworkをweakでリンクしないとダメ
1:06 PM
なんか素敵なworkaroundないですかね。これspmのライブラリだと、ライブラリ利用者側に責任がまわってしまうので
1:06 PM
より顕著
Avatar
Kishikawa Katsumi 2/26/2020 7:13 PM
問題を間違って捉えてるかもしれないですが、 CombineやSwiftUIを使ってるライブラリで、でもそれは内部でうまいことハンドリングしていて下のバージョンでも使えるようになっている、ということですよね。 でその際、下のバージョンで利用したい場合は、利用者側がWeak Linkの設定をする、というのはそういうもので、合理的に見えます。
Avatar
えっと。
7:34 PM
そういうものじゃないはずなのにバグでそうなってしまってるってことですかね。
7:37 PM
SwiftUIやCombineを使うライブラリで、そのmin target platformが例えばiOS 11とかになってる場合、そのライブラリの実装では@available()や#available()でAPIを制限すれば、本来なら、そのライブラリを使う側は-weak_frameworkなしに@importするだけで(dyldのframwork linkの力で)自動でシンボルがweakになってランタイムで問題なく実行できるんです
7:38 PM
しかしどうやらcombineやswiftuiにはいくつか問題のあるシンボルがまだ含まれているようで、それらがweakリンクにならないんです
7:38 PM
nm -mgでバイナリみるとわかると思います
7:39 PM
そういう漏れ出たシンボルはそのライブラリを利用してる側からは知ったこっちゃないことなのに-weak_frameworkしないと実行時にdyldがcrashするということになってしまって
7:39 PM
あらウザイ
7:39 PM
ってことです
7:39 PM
Tweetのスレッドでも参照していますが、https://forums.swift.org/t/weak-linking-of-frameworks-with-greater-deployment-targets/26017/11 Just WorksTMらしいんですよね本来は (edited)
That should definitely work as long as the framework is indeed weak-linked. If it's not, then either the SwiftUI folks have missed some availability (more likely), or there's something in Xcode (or in your project specifically?) that's causing it to be strong-linked. It's supp...
7:40 PM
そうなってないんだけど。
Avatar
Kishikawa Katsumi 2/26/2020 8:09 PM
なるほど、Availabilityでちゃんと下のバージョンで使わないようにコントロールしたらdyldはweak linkとして扱ってくれる(はず)なんですね。
Avatar
ですです
9:34 PM
そうなってないんだけど
Avatar
Xcode 11.4 beta 2で直ってるような。
11:19 PM
Combine.swiftmodule/x86_64.swiftinterface のdiffを見ると、いくつかのextensionに@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)が追加されてて、 +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Subject where Self.Output == Swift.Void { public func send() } ってのも含まれてる。 (edited)
Avatar
combineはなおってます
Avatar
生成されるバイナリのnm -mgでも (undefined) weak external (extension in Combine):Combine.Subject< where A.Output == ()>.send() -> () (from Combine) になる。
Avatar
SwiftUIは治ってない
11:23 PM
直ってない
Avatar
なるほど。
Avatar
SwiftUIのswiftinterface内で@availableが付いていないのを全部チェックしないといけないのですね。
11:31 PM
型の@availableよりextensionの@availableが緩かったらコンパイラでエラーにして欲しいな。
Avatar
extensionが同じバイナリ/moduleのなかにあるかどうかわからないんじゃないですかね
Avatar
Improvementなissueあった。 > コンパイラでエラーにして欲しい https://bugs.swift.org/browse/SR-10897 (edited)
Avatar
とりあえずコメントしてvoteしておいた。 (edited)
Avatar
おー。
Avatar
Deleted User 5/25/2020 7:43 AM
今年のGWより独学でSwiftUIを勉強し始めた者です。みなさんの視点からみて、SwiftUIの将来性はどう思われますか?
Avatar
Kishikawa Katsumi 5/25/2020 7:53 AM
将来的にはiOS, Mac等のAppleプラットフォームの開発環境として標準になっていくと思います。
Avatar
Deleted User 5/25/2020 7:57 AM
回答ありがとうございます。1ヶ月ほど勉強した感想なのですが、Swiftをしらない初学者がいきなりSwiftUIをやるのは、やはり難易度が高いでしょうか。おすすめの勉強法等あれば教えていただきたいです。
Avatar
Kishikawa Katsumi 5/25/2020 8:01 AM
SwiftUIをやりながらSwiftを覚えていけるとも思うのでなんともいえないですが、SwiftUIはまだ既存の開発手法に比べて情報が少ないし、そもそもできないことも多いので作りたいものによっては向いていない恐れはあります。 SwiftUIを勉強したいのか、何か具体的なアプリなどを作りたいのか、Swift言語を勉強したいのか、などによると思います。 はっきりしてなくてもいいのですが、勉強しようと思った目的はどういったものでしょうか?
Avatar
Deleted User 5/25/2020 8:07 AM
勉強の目的は「自分でiPhoneアプリを作れるようになりたい」という思いからです。普段使っているデバイスに自分好みのアプリが入れられたら最高だなと。また個人開発ができるようになったら、副業として収入を得ることを目標にしています。
Avatar
Kishikawa Katsumi 5/25/2020 8:21 AM
それなら実用的な観点でいうとSwiftUIにこだわらず、とっつきやすそうな本を2、3冊入手して作ってみるのがいいと思います。SwiftUIは必要に応じて、特に何もなければ1、2年後くらいにはもっと情報の入手性も上がってるのでそれくらいでもいいと思います。
Avatar
Deleted User 5/25/2020 8:25 AM
なるほど。目標を「アプリを作る」に絞ったほうがいいということですね。ネットを見る限り現場の方も手探り状態のようなので、気長に続けようと思います!
8:26 AM
回答ありがとうございました!
Avatar
あと、インターネットにあふれているSwiftUIの情報のかなりのものが古くて、APIが変わっていたりすでに陳腐化した手法だったりするので(AppleのWWDCのドキュメントも含む、紙媒体は特に古い可能性が高い)、かならず最新の情報かどうかを確認してください。 (edited)
12:15 AM
数ヶ月前の情報を見ていると時間をかなり無駄にします。 (edited)
12:17 AM
あと、1ヶ月くらいで(Imaginaryな)WWDCなので、いまは情報や知識が一瞬で陳腐化する可能性が高いです。 (edited)
12:19 AM
これはいつにおいてもそうなのですが、特に今は長期的な知識として体系的に構造を理解して勉強する、表面的なAPIに惑わされないなどの勉強の工夫が必要だと思います。
😻 1
Avatar
まだまだ発展途上ということですね。構造や根底にある考え方を優先して勉強していこうと思います。
10:37 AM
ありがとうございます!
😊 1
Avatar
ページの一部分を横スクロールできるようにしたアプリってよくありますけど( App Store とか Apple TV とか Prime ビデオとか)、そんな感じの UI で横スクロールしたときに Safe Area の外にはみ出して表示したいと考えています(特に iPhone X タイプのノッチがある端末で Landscape のとき)。ただ、普通にやると Safe Area の外はクリップされてしまい、 edgesIgnoringSageArea にすると(そもそも Safe Area がなくなるので)スクロールしてない状態でも最初から Safe Area の外にはみ出てしまうという状況です。 SwiftUI でうまくやるにはどうすれば良いでしょうか?
Avatar
Kishikawa Katsumi 5/27/2020 1:15 AM
Pure SwiftUIでできるかなあ
😂 1
Avatar
うーん、今取り組んでるアプリで SwiftUI で作った方が楽そうな部分があって、意外と SwiftUI ベースで必要なところだけ UIKit 埋め込みでいけないかと考えてたんですが、 UIKit ベースで楽になるとこは SwiftUI 埋め込みの方が現実的そうですね・・・。 (edited)
Avatar
Kishikawa Katsumi 5/27/2020 1:21 AM
ページの一部分を横スクロールはCollectionViewとCompositional Layoutの方がだいぶ簡単だと思います。
Avatar
動くとこまでは SwiftUI でも簡単だったんですけどね😢 Safe Area みたいな細かいところが・・・。
Avatar
Kishikawa Katsumi 5/27/2020 1:25 AM
SwiftUIで細かいところが気になりだすと、だいたいコンポーネントを一から自作することになります 😅
😭 1
1:27 AM
プリミティブはよくできててUIKitより断然使いやすいと思いますが、コンポーネントはだいぶ基本機能が足りない、、、
Avatar
View の再利用とかめちゃくちゃやりやすいですしねぇ。いいところはいいんですが・・・。今回は、 Table View ( List )のセルの操作が結構入りそうな箇所があり、そこを差分計算でやってもらった方が楽そうだなと。そこだけ SwiftUI 埋め込みする方針で検討してみます。
Avatar
Kishikawa Katsumi 5/27/2020 1:33 AM
そこはDiffableDataSourceで。。。UIKitの方がいいと思う。
1:33 AM
たぶん。。
Avatar
UIKitなら全部UIKitでできるよ?
😅 1
Avatar
おおお、 UITableViewDiffableDataSource とかできてたんですね。把握できてませんでした。部分的にでも SwiftUI 使ってみたい気持ちもありますが、これも検討してみます。
Avatar
根っこを SwiftUI で作るのは怖いから、 UIViewController ベースにして、使えそうなところは UIHostingViewConroller に入れて使う形を試してたら、これまで GeometryReader の中にあった NavigationViewUINavigationController として外に出すことになって、そうすると geometry が変化しまくって GeometryReader 以下の再描画が走りまくることに・・・。
👀 2
Avatar
UIHostingViewController 使わずに UIView ベースにするとなんか解決しそうですね...!
6:10 PM
雰囲気、SwiftUI は多分逆を想定してる気がするんだよなあ。
6:11 PM
好きなように migrate できるよてきな言い分をしてたけど。
Avatar
import Combine class Foo: ObservableObject { @Published var a: Int = 0 @Published var b: String = "" static let shared: Foo = .init() } import SwiftUI struct ContentView: View { @ObservedObject var foo: Foo = .shared let cancellable = Foo.shared.objectWillChange.sink { _ in print("change") } var body: some View { print("body") return VStack { Text("a: \(foo.a)") Button("+") { Foo.shared.a += 1 } Text("b: \(foo.b)") Button("+") { Foo.shared.b += "+" } Button("++") { Foo.shared.a += 1 Foo.shared.b += "-" } } } } ↑のようにして、 ++ ボタンを押したときに FooobjectWillChange には 2 回値が流れる( "change" は 2 回表示される)けど、 "body" は 1 回しか表示されない。なんとなく、 @State とか @ObservedObject とかは変更が生じる度に body が実行されるのだと思ってました。当たり前かもしれないけど、ちゃんと setNeedDisplay みたいな仕組みが備わってるんですね。
Avatar
確か元々didSetで通知だったのがwillSetで通知に変わりましたよね
10:33 AM
多分その辺りでsetNeedsDisplay相当の処理になったんじゃないかと考えています
Avatar
なるほど。当時リアルタイムで追ってなかったのでそのあたりの変遷をよく知らないんですよね😅
Avatar
質問です。 read-only な Binding (のようなもの)が欲しくなることってないですか?変更を通知して子ビューに反映させたいけど、子ビュー側から変更してほしくないようなケースで。 set を空にする extension でも書けば良い? あと、 class Foo: ObservableObject があったときに、 @ObservedObject var foos: [Foo] 的なことしたくなりませんか? @ObservedArray とかがあればいいのかな? もしくは、僕が知らないだけで、↑を実現する方法が標準で提供されていたりしますか?軽くググっても見つけられなかったんですが・・・。
Avatar
@Binding なのを見せないでなにか違うものを見せるのかなあ?
Avatar
とりあえず↓みたいなものを作ったらできたのはできたんですが、 import SwiftUI extension Binding { static func readOnly<Value>(_ binding: Binding<Value>) -> Binding<Value> { .init(get: { binding.wrappedValue }, set: { _ in }) } }
10:20 PM
そもそも ObservableObject の方で↓みたいに read-only にしたい場合は、 $foo.a みたいにして Binding が取れないから不便ですね・・・。 class Foo: ObservableObject { @Published private(set) var a: Int = 0 ... }
10:22 PM
@ObservedObjectprojectedValue 以外の read-only のための API を作って、 _foo.readOnly.a みたいに取得できるようにすればいいのかな。 (edited)
Avatar
できた。 import SwiftUI extension ObservedObject { var readOnly: ReadOnlyWrapper { .init(self) } @dynamicMemberLookup struct ReadOnlyWrapper { private let object: ObservedObject<ObjectType> init(_ object: ObservedObject<ObjectType>) { self.object = object } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { Binding(get: { self.object.wrappedValue[keyPath: keyPath] }, set: { _ in assertionFailure("Read-only") }) } } }
10:36 PM
あー、 WrapperreadOnly 生やして $foo.readOnly.a みたいな方がいいか。
Avatar
だめだ。 KeyPathWrapper のための ReferenceWritableKeyPath に変換できない。 import SwiftUI extension ObservedObject.Wrapper { var readOnly: ReadOnly { .init(self) } @dynamicMemberLookup struct ReadOnly { private let wrapper: ObservedObject<ObjectType>.Wrapper init(_ wrapper: ObservedObject<ObjectType>.Wrapper) { self.wrapper = wrapper } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { get { wrapper[keyPath: keyPath] } // ⛔ set { assertionFailure("Read-only") } } } }
10:49 PM
Wrapper がラップしてる元のオブジェクトを参照できればいいんだけど・・・。
10:50 PM
あ、 subscriptget, set じゃなくて getBindingset をつぶさなきゃいけないか。どっちにしろダメだけど。 (edited)
10:53 PM
WrapperreadOnly 生やして $foo.readOnly.a は諦めて、少々ブサイク(?)だけど ReadOnlyWrapper 方式で _foo.readOnly.a にするか。 (edited)
Avatar
foo.a.readonlyはどうですか? Bindingから弱める
Avatar
$foo.a.readOnly やんね?たしかにそれならできるかも。
Avatar
あー、それ最初に検討したけどダメだったんだ。 a が read-only のときにそもそも $foo.a が作れないんだった。
2:53 PM
.lazy とのアナロジーで考えるとやっぱ Wrapperreadonly 付けたい気がするなぁ。大元に付けることで下流の性質が変かする。まあ、 _foo.readOnly でも同じなのかもしれないけど。
Avatar
$foo.readOnly.a できた。この方法絶対ダメだけどw import SwiftUI extension ObservedObject.Wrapper { var readOnly: ReadOnly { let object: ObjectType = unsafeBitCast(self, to: ObjectType.self) return ReadOnly(object) } @dynamicMemberLookup struct ReadOnly { private let object: ObjectType init(_ object: ObjectType) { self.object = object } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { Binding<Subject>( get: { self.object[keyPath: keyPath] }, set: { _ in assertionFailure("Read-only") } ) } } }
3:04 PM
Wrapper はどう考えても元のオブジェクトをラップしてるだけだろうと考えて unsafeBitCast したら動いたw
3:10 PM
今やりたいのは、↓の Foo のような型に対して @ObservedObject var foo: Foo を作って、 $foo.readOnly.a のようにして read-only な片方向のバインディングを作りたいということです。 import Combine class Foo: ObservableObject { @Published private(set) var a: Int = 0 func incrementA() { a += 1 } }
3:12 PM
今は incrementA が露出してるけど、アップデートのための関数は生成者や internal にしか暴露されないとかもあり得る。そうでなくても、↑の incrementA のようにアップデートの方法を限定したいからプロパティが private(set) になるケースもあって、そのようなケースでもバインディングしたいというニーズも考えられる。
Avatar
もっといい方法があるかもしれないから聞いてみた。 https://forums.swift.org/t/one-way-data-binding-from-observedobject-with-swiftui/37547
Is it possible to achieve one-way data binding with SwiftUI in the situation like below? import Combine final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } func reset() { count = 0 } } import SwiftUI...
Avatar
あああ、根本的に勘違いしてた。こんなことする必要ないのか・・・。当たり前だ・・・。
4:35 PM
If it's read-only, you should pass it as normal let value
https://forums.swift.org/t/one-way-data-binding-from-observedobject-with-swiftui/37547/2 これに気付かなかったのアホすぎる・・・。
4:37 PM
なんで気付かなかったんだろう。
Avatar
omochimetaru 6/14/2020 3:24 AM
え、これは受け側で書き込まないように自粛してるだけの書き方ではないですか?
3:24 AM
やりたいのは渡す側で書き込み禁止を表明して、静的に安全にしたいのではなく?
Avatar
受け側( NumberDisplay )は let number: Int で受け取るから書き込みできなくない?
Avatar
omochimetaru 6/14/2020 3:34 AM
はい。やりたいことはそうではなくて
3:35 AM
渡す側(counter.countって書く側)で、書き込めない型にする事だと思ってました
Avatar
Int は書き込めない型じゃないかな?
3:36 AM
Binding<Int> は書き込めるけど。
3:37 AM
ああ、日本語のパースにミスった。
3:37 AM
「(渡す側で書き込めない)型にする」か (edited)
3:37 AM
「(渡す側で)(書き込めない型にする)」かと思った。
Avatar
omochimetaru 6/14/2020 3:37 AM
それは確かにそうだけど、それはただ値を渡してるだけで、 今度は、変更通知が伝わらない
Avatar
いや、伝わるよ。
Avatar
omochimetaru 6/14/2020 3:38 AM
読める型、読み書きできる型、変更通知の3つの機能のうち、読めるけど変更通知できる2つだけほしいのですよね
Avatar
ContentView@ObservedObject として counter を保持しているから、
3:38 AM
counter が変更されたら ContentViewbody が走って、
3:38 AM
NumberDisplay(counter.count) が作り直されて、
3:39 AM
NumberDisplay も更新される。
3:39 AM
仮想ビューだから作り直す前提だからそれで問題ない。
Avatar
omochimetaru 6/14/2020 3:42 AM
あれ、SwiftUI的にはそれでいいのか。 社内チャットで書いていた↓でいうところの
仮にストア自体を ObservableObject にしても、個々の値を監視したいことはあるから、↑の Store が ObservableObject なだけではダメなんよね。 関係のない更新が走りまくってしまう。
「関係のない更新が走りまくってしまう」に該当しているけど、それはいい?
(edited)
Avatar
N:1と1:1は問題を切り分けた方がいい気がする。
3:47 AM
もし ContentView@Published var numbers: [Int] を持つ ObservableObject@ObservedObject で保持していて、
3:48 AM
そのうちひとつだけを NumberDisplay(numbers[0]) とかで表示していると良くなさそう。
3:49 AM
でも、 numbers 全体を表示してる( ContentViewnumbers.count 個の NumberDisplay を表示している)なら必要な更新だから問題ないと思う。
Avatar
omochimetaru 6/14/2020 3:57 AM
もしCounterが class Counter: ObservedObject { @Published var count: Int @Published var foo: Foo } のようにfooも持っていてもこれはclassだから分離されているから良いけど・・・ (edited)
3:59 AM
class Counter: ObservedObject { @Published var count: Count } struct Count { var value: Int var foo: Foo } (edited)
4:00 AM
のような場合にcount.valueが変わると、これはcount自体の変更になって、 count.fooの更新通知も発生してしまうのが嫌だよねという話かと思ってたけど
4:01 AM
これはもうstructの部分更新が全体更新と同一視されるのは基本的な事だからそれでいいのかな。
4:01 AM
この状況において $counter.count.value でバインディングしたときに、$counter.count.foo でバインディングしている側には影響が無いようなシステムを期待しているイメージだった。
4:02 AM
SwiftUIの場合は実装的には、$counter.count.foo をバインドしてるView Tree側で、勝手に変更チェックをしてfooが変化していなければ実View(FooView)の更新はしないという事をやるのだと思うけど。 (edited)
Avatar
どちらにせよ親側(ContentView)が@ObservedObjectでCounterを保持している時点で、valueが変わってもfooが変わっても親側のbodyから再生成なんよね。 (edited)
Avatar
omochimetaru 6/14/2020 4:08 AM
あら、違うかな、 そのときに $counter.count.foo が書き込みインターフェースを提供しない型になるというのがやりたいことだったけど、 counter.count.foo ($を外す) を使えばいいよねという話で解決したという事か
4:09 AM
@ObservedObject var counter で掴んでる根っこのところから、毎回出発するからそれはそうなのか。
Avatar
うん、そうなんよね。なんでそれに気付かなかったのか…。仮想ビューの考え方にちゃんと頭の切り替えができてなかった。
Avatar
omochimetaru 6/14/2020 4:16 AM
う〜むなるほど。僕もわかってなかった😅
4:17 AM
なんかproperty wrapperと通知の仕組みがあるからそこに動的なインスタンスがある事を意識してしまうけど、セマンティクスとしては普通のvarなのが難しいな・・・
Avatar
言われてみれば当たり前だけど発想の転換が必要だったね。もう少し慣れねば💦
Avatar
Deleted User 6/14/2020 1:43 PM
シンプルなリマインダーアプリを作ってるんですが、Listについてわからないことがあります。「キーボードを表示しているときに、適当なところをタップするとキーボードを閉じる」という動作を実装したいんですが、この動作をできるようにすると、List編集中のDeleteが効かなくなってしまいます。
1:43 PM
List { ForEach(text, id: \.self) { user in Text(user) }.onDelete{ offset in    self.text.remove(atOffsets: offset) }.onMove{ source, destination in self.text.move(fromOffsets: source, toOffset:destination) } } .onTapGesture { UIApplication.shared.endEditing() } (edited)
1:45 PM
extension UIApplication { func endEditing() { sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil ) } } (edited)
1:48 PM
よければ原因を教えていただけると助かります、、
Avatar
cornerRadiusモデファイア氏、内容がネガティブパディング付いているとわかりやすいですが、勝手に子Viewにclip掛けちゃうんですね… (edited)
7:32 AM
7:32 AM
7:32 AM
7:38 AM
cornerRadiusの内部的には、返すViewを生成するときに、clipShape()とか、またはRoundedRectangle作ってclip()みたいなのと同じようなことが行われているのかな
Avatar
これはUIKitでも同じ事が起きますね。大枠の仕組みは変わってないのだと思います
🍵 1
😴 1
🤔 1
Avatar
UIKitだとこんな感じで clipsToBoundsmasksToBounds がデフォルト false なので cornerRadius を掛けたとしてもちゃんとはみ出すので、それと同じノリでSwiftUIでもはみだしてくれるやろと雑に突撃して撃沈した感じです😇 (↑のSwiftUIに条件を揃えたpaddingによるはみ出しではないですが) (edited)
10:47 AM
(いちおうSwiftUIでも条件を揃えてpaddingではなくFrameではみ出させてもClipする) (edited)
Avatar
import SwiftUI struct ContentView: View { @ObservedObject var counter: Counter = .shared var body: some View { print("ContentView.body") return NavigationView { VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } NavigationLink(destination: AView(count: counter.count)) { Text("Open A") } } } } } struct AView: View { let count: Int var body: some View { print("AView.body") return VStack { HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } NavigationLink(destination: BView(count: count)) { Text("Open B") } } } } struct BView: View { let count: Int var body: some View { print("BView.body") return HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } } } final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() }
4:04 AM
@ObservedObject の影響範囲を調べてて、↑で AView を開いてから + ボタン押しても変更が反映されるのがおどろきだったんですが、さらに BView を開いて + ボタンを押しても変更は反映されないし、そこから Back して AView に戻ってから + ボタンを押しても今度は変更が反映されないんですが、挙動がおかしくないですか? (edited)
4:06 AM
最初に AView を開いたときに変更が反映されるのがおかしいのか、それとも AView に戻ってきたときや BView で変更が反映されないのがおかしいのか。
Avatar
omochimetaru 6/25/2020 4:12 AM
AView, BView は Counterについての依存が見えない定義になっているから、ボタンを押しても何も起きないのはわかるけど
4:12 AM
最初に開いた後にボタンを押すと反応するのが不思議ですね
4:13 AM
ContentView.body の再描画が起こりつつ NavigationLink の遷移状態が継続しててAViewが再描画されてそう (edited)
4:14 AM
Navigationに関する一つ前までがステートツリーとして監視されているという実装だったとしたら現象の説明がつきそう。
4:14 AM
左上に前の画面のタイトルが表示されているから・・・
4:14 AM
で、そこは 前の画面の .navigationTitle() modifierだから
4:15 AM
一個前と今の画面だけがステート監視されてるみたいな。
4:16 AM
AからBに遷移した後は 根っこのContentView のステートは捨てられてるからCounter.countがイベントを発行してもBの再描画が起きない。
4:17 AM
なにがおかしいのかでいうと、 Counter.shared ってSwiftUIシステムから感知できない形で依存性をもってしまっている事じゃないですかね
4:18 AM
SwiftUI的には前提が壊れるからあらゆるバグが起きてもヨシという扱いだと思います。
Avatar
なにがおかしいのかでいうと、 Counter.shared ってSwiftUIシステムから感知できない形で依存性をもってしまっている事じゃないですかね
えーでも、Counter自身はObservableObjectだし、countの変化がSwiftUI以外の要因(サーバー側から変化が送られてくるとか)パターンを考えると別に Counter.shared はそれをエミュレートしてるのと変わらない気がするので、納得いかないです
Avatar
omochimetaru 6/25/2020 4:53 AM
Counter.shared をいきなり触るんじゃなくて、 AView と BView に @ObservedObject として引数で渡すとか、 @EnvironmentObject だっけ? として渡さないといけないのでは
Avatar
うーん、AViewもBViewもコンストラクタで渡されたものを表示するだけの静的なコンポーネントと考えると、その前提もおかしい気はするんですよね。これってNavigationLinkがなかったら、たぶんAViewもBViewも更新されるはずですよね。
4:57 AM
NavigationLinkの代わりに、ContentViewの中に子供としてAViewがあり、AViewの中に子供としてBViewがある場合。
Avatar
omochimetaru 6/25/2020 4:57 AM
表示するだけじゃなくて、クリックして外界に影響を与えるので
4:57 AM
その表明が必要じゃないかなと
4:58 AM
その表明が無い事によって、SwiftUIエンジンが、状態更新の起点を見逃してしまうという挙動
Avatar
クリックというのはナビゲーションのこと?カウンターをあげる方のこと?
Avatar
omochimetaru 6/25/2020 4:58 AM
ああ、そっか2つあるのか。カウンターをあげる作用のことです。
Avatar
NavigationLinkの代わりに、ContentViewの中に子供としてAViewがあり、AViewの中に子供としてBViewがある場合。
この場合、BViewも更新されるんちゃうんと勝手に思っていたけど、実際どうなんだろう。子供でもBViewが更新されないのなら納得はいきますね。たしかに。
Avatar
omochimetaru 6/25/2020 5:01 AM
子供でも同じじゃないですかね 変わってくるのはAViewとかBViewの型をつくらずにそのbodyの中身を書き下した場合。
Avatar
import SwiftUI struct ContentView: View { @ObservedObject var counter: Counter = .shared var body: some View { print("ContentView.body") return NavigationView { VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(count: counter.count) } } } } struct AView: View { let count: Int var body: some View { print("AView.body") return VStack { HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } BView(count: count) } } } struct BView: View { let count: Int var body: some View { print("BView.body") return HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } } } final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
5:05 AM
子供だと、どの + を押しても全部更新されました。 (edited)
Avatar
omochimetaru 6/25/2020 5:06 AM
なるほど・・・
Avatar
やっぱりNavigationがどうも怪しい気がする
Avatar
omochimetaru 6/25/2020 5:06 AM
動く側に倒れるのは嫌だな・・・
Avatar
AViewが作り直されるなら、そのbodyがイニシャライザの引数の違うBViewを作って返している以上、BViewの部分の表示は変わって欲しいんですけどね。
5:09 AM
あ、その前に、ContantViewが作り直されるならそのbodyがイニシャライザの引数の違うAViewを作って返している以上、AViewも作り直されて欲しい、を言うのが抜けてた。
5:15 AM
+ボタンはいったんなしで考えて(外部要因でCounterが更新されるとして)、その上で、AViewやBViewが直接Counterとは関係ないものを考える。例えばBViewは任意の文字列の先頭数文字を表示するという機能を持つViewで、AViewは任意の文字列の先頭を表示しつつ、なにか装飾を加えて表示するViewだというような汎用コンポーネント。すると、それを利用するContentViewのみ、Counterを見て表示を更新してる、というのは自然だと思うんですよね。
Avatar
omochimetaru 6/25/2020 5:17 AM
そのAViewとBViewにわたす元になる文字列を変更する動作の起点となるUIイベント(クリックハンドラ等)が ContentViewの中にあるのであれば、そのハンドラの中から触るのはContentViewの@EnvironmentObject 等に限定されているべきで、一般のグローバル変数は触ってはいけないんじゃないかなあ。
Avatar
ContentViewはObservableObjectのCounterをObservedObjectで見てるから、UIイベントがなくても、CounterのobjectWillChangeが発火すれば、作り直されるべきだと思うんですよね。
Avatar
omochimetaru 6/25/2020 5:21 AM
なるほど。そこは、そうですね。
Avatar
.sharedってなってるからグローバル変数感がありますが、Counterのインスタンスを作って、それがタイマーとか、サーバーからの通知とか、そういうのでobjectWillChangeが発火するイメージ
Avatar
omochimetaru 6/25/2020 5:23 AM
AViewとBViewがContentViewの子になってるときは、それでうまくいってるという事か。
Avatar
そうですね。
Avatar
omochimetaru 6/25/2020 5:23 AM
やっぱりNavigationViewが出てきて画面遷移がBまで行くと、ContentViewのobjectWillChangeの購読が中止されてそう
Avatar
画面遷移がAの時点でContentViewの方は変化しなくなるのならNavigationとはそういう挙動として納得できるのだが。 (edited)
Avatar
omochimetaru 6/25/2020 5:24 AM
画面遷移後も objectWillChange によって見た目が更新されて欲しければ、AViewもCounterを受け取るべきということかな
5:25 AM
Aの時点で更新される挙動はポリシーが一貫してない結果だけど
5:26 AM
僕はそこは怪しい挙動に突入しているのであってAViewが自分で購読するのが正しい書き方という規約なんじゃないかと思います
Avatar
実はぼくもちょっと前に、画面遷移したら前の画面の更新が次の画面に伝わったりはしない方を期待してたんですよ
Avatar
omochimetaru 6/25/2020 5:28 AM
もしBViewが @EnvironmentObject でCounterを参照していたとしても、このパターンだとやっぱり更新されない気がしてきた BViewにとってのcountがletなので。
5:29 AM
BView.bodyが @EnvironmentObject 経由で値を見ているべきで、 ContentView → AView → BView の過程の値の加工パスは前提にしちゃいけないような気がする
Avatar
えーそれだと、「BViewは任意の文字列の先頭数文字を表示するという機能を持つView」のような再利用可能なコンポーネントが常にEnvironmentObjectかBindableを受け取らないといけなくなっちゃう気がする。
Avatar
omochimetaru 6/25/2020 5:31 AM
画面遷移の場合はそうってことになるんじゃないかな
Avatar
ああ、画面遷移の場合は、っていう条件付きで、ですね
Avatar
omochimetaru 6/25/2020 5:32 AM
子の場合は親のbodyの再描画によって書き換わって再利用できるからOK
Avatar
そう。伝わらないこと前提がいいんですが、伝わるパターンがあるのが困る。
Avatar
omochimetaru 6/25/2020 5:32 AM
だから、「画面として機能させたい」のかどうかという意識をする必要が出てくるのかな。
5:32 AM
設計する上で。
5:33 AM
伝わるパターンがあるのはSwiftUIが壊れてるのかプログラマが規約違反してるのかはっきりしないから嫌ですね。
Avatar
さっき書きかけたんですが、
5:34 AM
X画面(XViewと、それがObservedObjectで見るXViewModel)とY画面(YViewとYViewModel)を独立して作ってて、X画面からY画面へNavigationLinkで遷移するようにしてたんですね
5:36 AM
で、XViewModelがまあまあ無駄にメモリ食う感じのするものを持っていたので、XViewが表示されてるとき以外はXViewModelの中でそれを持つのをやめようと思って、XViewのonDisappearのタイミングでXViewModelのそのでかいやつを消すようにしたんです
5:37 AM
そしたら、X画面からY画面へ遷移したあとにXViewのonDisappearが呼ばれて(ここまでは想定通り)、それによりXViewModelのPublishedな変数を更新したら、X画面も作り直されて、その結果、NavigationLinkのある部分が消えて、Y画面にいることができなくなり、X画面へ戻るという挙動に
Avatar
omochimetaru 6/25/2020 5:38 AM
前画面のNavigationLinkが消えると次画面から前画面に引き戻される?
Avatar
それを見て、あー、画面遷移しても、前画面は全部生きてるのかー、根本的に認識を間違えてたわーと思ってたとこだったんで。
5:38 AM
そう、戻ります。
Avatar
omochimetaru 6/25/2020 5:39 AM
なるほど。合わせると、3画面目で1画面目のLinkが消えるロジックが入ってても1画面目の再描画が走らなくて大丈夫みたいな事が起こりそうですね。 (edited)
Avatar
それは困るんですよね。認識しづらい。
5:40 AM
どっちかにカチッと決まってくれないと
Avatar
omochimetaru 6/25/2020 5:41 AM
そうですねえ。
5:41 AM
やっぱりライブラリとしての抽象度が高すぎるんだよな〜 (edited)
Avatar
あれでも、1画面目のNavigationLinkのisActiveを、EnvoronmentObjectに持たせた変数にBindしておいて、3画面目でその変数をfalseにしたら1画面目まで一気に戻れるぞ、というのをこのまえ確認したんだけどなw
Avatar
omochimetaru 6/25/2020 5:43 AM
ww
Avatar
確認したつもりでしたが、今やってみると動きませんでしたw あれれw
5:52 AM
2画面目までしかやらなかったのかな。
Avatar
omochimetaru 6/25/2020 5:54 AM
動かないのであれば、一貫性?はありますね。
Avatar
いやな一貫性だw
🦴 1
6:00 AM
struct ContentView: View { var body: some View { print("ContentView.body") return NavigationView { XView() } .environmentObject(Env()) } } struct XView: View { @EnvironmentObject var env: Env var body: some View { NavigationLink(destination: YView(), isActive: $env.isRootNavitaionActive) { Text("To Y") } .navigationBarTitle("X") } } struct YView: View { @EnvironmentObject var env: Env var body: some View { VStack { NavigationLink(destination: ZView()) { Text("To Z") } Button(action: { self.env.isRootNavitaionActive = false }) { Text("Tap this") } } .navigationBarTitle("Y") } } struct ZView: View { @EnvironmentObject var env: Env @State var text: String = "(Before tap)" var body: some View { VStack { Text(text) Button(action: { self.text = "(After tap)" self.env.isRootNavitaionActive = false }) { Text("Tap this") } } .navigationBarTitle("Z") } } final class Env: ObservableObject { @Published var isRootNavitaionActive = false }
6:00 AM
うん。2画面目だと戻るけど、3画面目だと戻らない。一貫性があります 😢
Avatar
omochimetaru 6/25/2020 6:03 AM
試してみた ZViewでtap thisおすとXまで戻りますよ
Avatar
うそーん。。。
Avatar
omochimetaru 6/25/2020 6:04 AM
6:04 AM
プロジェクト新規作成して↑のコードをコピペ。
Avatar
もしかしてiOS 14だと戻ったりする?
Avatar
omochimetaru 6/25/2020 6:05 AM
6:05 AM
iOS13だとビルドできない・・・
6:06 AM
あ、古いXcodeでやってみればいいか
6:08 AM
お。
6:08 AM
iOS13.5だと、Zからは戻らないですね。
Avatar
も、もしかしてiOS 14だとkoherさんのやつもBViewまで更新されるのでは
6:10 AM
試してみました。更新されますね
Avatar
omochimetaru 6/25/2020 6:10 AM
僕も確認できました。 (edited)
6:11 AM
これはどうなんだ・・・
Avatar
えーそんなんアプリ作れないよー
Avatar
omochimetaru 6/25/2020 6:11 AM
ここまでの話はもはや空論で、iOS14では、Navigationスタックを全部くしざして
6:11 AM
ちゃんと根本のNavigationViewのあるところから、再描画されるという
6:11 AM
仕様だと思ってしまっていいのか??
Avatar
まあそれが仕様なら納得はできる仕様です。2画面目までだけとかじゃないから。
Avatar
omochimetaru 6/25/2020 6:13 AM
そうですね。
Avatar
ただ、iOS 13ではそう動かないのだと、ちゃんとしたアプリを作るにはちょっと困るw こういう確認して遊んでる分にはいいですが
Avatar
omochimetaru 6/25/2020 6:14 AM
SwiftUIはiOS14が普及してから使いましょうという話になってしまうな。
Avatar
去年6月 よーしiOS 13が普及してから使うぞ。2年後だ! 今年6月 よーしiOS 14が普及してから使うぞ。2年後だ!
👆 1
Avatar
omochimetaru 6/25/2020 6:15 AM
完全にデジャブ
6:16 AM
一応iOS13.6で挙動が揃うとかはありえるか。
Avatar
お、そうですね。
Avatar
omochimetaru 6/25/2020 6:17 AM
13.6なら入れてもいいけど14は見送る人はまあまあ居そう。
Avatar
Xcode 11.6でやってみます
6:18 AM
ダメでした 😭 (2画面目までしか伝わらない) (edited)
😩 1
Avatar
見てない&ご飯食べてる間にすごい伸びてた💦
Avatar
yutailang0119 6/25/2020 6:21 AM
SwiftUI 2.0はiOS 14+です
Avatar
omochimetaru 6/25/2020 6:21 AM
1.0到達してるのかしら・・・
Avatar
yutailang0119 6/25/2020 6:23 AM
SwiftUI 5くらいまでは、毎年メジャーリリース
Avatar
やっと読み終わった。
6:27 AM
これ、おもしろいことがいくつかあって、
6:28 AM
BView でボタン押すと count は増えないけど、 ContentView.body は発火してるんですよね。
6:28 AM
print で確認できる。
6:29 AM
あと、 AViewcount プロパティを作らなかったら、変更が反映されない。これは理解できる。
Avatar
まあiOS 14の挙動なら理解できるので、iOS 14が来たら起こしてください…
😴 1
😂 1
Avatar
count プロパティがないと View の差分がないので AView の再描画が走らないのかと。
Avatar
omochimetaru 6/25/2020 6:33 AM
なんかそれは言われてますよね
6:33 AM
bodyの再生成と、再描画の間に、diffチェックが有る
Avatar
NavigationLink の場合、クロージャではなく引数に直接 AView(count: counter.count) が渡されてるから、そこでつながってるのも理解できる。
6:34 AM
ContentView が再描画されるんだったら(これは iOS 13 & BView からでも確認済み)、 AView が新しい count で作られて、差分が発生するから再描画されてもおかしくない。
6:35 AM
二段階になったら更新が反映されなくなるのは、 iOS 14 の挙動を聞いてる限り、 iOS 13 のバグなのかなぁ。
6:36 AM
navigation 遷移したらそこで途切れるという仕様でもいいと思うけど・・・。
Avatar
omochimetaru 6/25/2020 6:36 AM
フォーラムで聞くといいのかも?
Avatar
今だと Lab とかがいいのかな?
Avatar
omochimetaru 6/25/2020 6:36 AM
#swiftpm のほうで rintaro さんが
6:37 AM
Avatar
なるほど。
Avatar
omochimetaru 6/25/2020 6:37 AM
swift forumじゃなくて apple developer forum
Avatar
あー、なるほど。
6:38 AM
この前、 Swift Forums でも数分で返ってきたけどね。あのやらかした恥ずかしい質問のやつ。
Avatar
omochimetaru 6/25/2020 6:40 AM
iOS13と14の違いをAppleがどういうつもりでやってるのかはAppleの方のフォーラムのほうが回答あるかなと思って。
Avatar
そうかも。
Avatar
bodyが返したものがツリーになって裏で仮想DOM的に保持されているはずだから、その仮想DOMツリーを表示するデバッガが欲しいですね。実際のView Hierarchyみたいな感じのやつで。
Avatar
そもそもiOS13のSwiftUIはマイナーバージョンアップでも動きが変わる感じでunstable過ぎたので
Avatar
struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView() .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView: View { var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count)") Button("+") { Counter.shared.increment() } } BView() .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView: View { var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } ↑これだと同一画面だけど AViewBView は更新されない( iOS 13 )。おもしろい。 (edited)
Avatar
14でマトモになるなら13の 下位互換は正直なくてもいいやの感ある (edited)
Avatar
これは更新されなくていいと思うんですよね。AViewやBViewの更新を発火するものがないから。
Avatar
はい、そう思います。
6:50 AM
AView に diff がないから body の発火まで行かないのかと。 (edited)
Avatar
何を基準にdiffのあるなしを見てるんでしょうね。
Avatar
でもなんか変な気もしてきたな。これって AViewBView を展開して ContentView2 に埋め込んだ場合は更新されますよね?
Avatar
bodyが呼ばれるかどうかじゃないですかね。
Avatar
SwiftUI って仮想ビューは軽量だから再描画には問題なくて、ツリー全体を再描画した上で diff を見て変化があった部分だけ本物の UI に反映するよって話ですが、
Avatar
AViewやBViewのイニシャライザは呼ばれているけど、AViewやBViewのbodyが呼ばれてないのだと思います。
Avatar
そこに二段階あって、 body が発火されるかと、 body を発火した上で diff があるかがあるってことですよね。
6:55 AM
はい、 body が呼ばれてないんですが、 body が呼ばれるかどうかの判断に、ツリー全体じゃなくて AView としての比較が入ってるってことですよね。
Avatar
仮想DOMの先駆者であるReactでもそれは同じで、ViewにあたるのがComponent、bodyにあたるのがrender()なんですが、そのrender()を呼ぶかどうかを軽量に決める、つまり、前状態とのdiffをとるメソッドがrenderとは別にComponentにあるんですよ。 (edited)
6:57 AM
ViewがEquatableならわからないでもないんですが、Viewってそうでもないから、いったい何を基準にdiffをとってるんだろうと。
Avatar
omochimetaru 6/25/2020 6:57 AM
リフレクションで動的にデータ構造を取得してそうです
Avatar
うーん、たしかに。しかも、 Equatable でないプロパティ持ってることもありますよね・・・。
Avatar
omochimetaru 6/25/2020 6:57 AM
言語ユーザーには提供されてないけどバイナリレベルでは安定仕様になったので。
6:59 AM
Equatableでないものや規定のpriperty wrapperじゃないものは無視するか適当にやってそう
Avatar
Equatable== は関係なさそう。
7:03 AM
import SwiftUI struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(value: .nan) .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView: View { let value: Double var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count), \(value)") Button("+") { Counter.shared.increment() } } BView(value: value) .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView: View { let value: Double var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count), \(value)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() }
7:03 AM
↑では AViewBViewvalue を持っていて、そこに .nan を渡してるから
7:03 AM
== で比較すると常に false になるんだけど再描画されない。
Avatar
.nan で常にfalseにする技だw
Avatar
omochimetaru 6/25/2020 7:04 AM
謎の方法でww
Avatar
もちろん、 valueDouble(counter.count) を渡したら再描画される。
Avatar
いやちょっとまってw その前にAViewとかBViewとかはEquatableじゃないから == で比較できます?
Avatar
プロパティをリフレクションで見て Equatable なものを比較してたらという仮説に対してですね。
Avatar
あ、いや、じゃなくて、本当に Equatable になってませんよ、って言ってます (edited)
Avatar
omochimetaru 6/25/2020 7:06 AM
Double型はEquatableという意味では
Avatar
ああそういう意味でか。
Avatar
はい、そういう意味でした。
Avatar
ぼくはAView自体をEquatableにしたらどうなんだろうと思いました。
Avatar
ちなみに、 AViewBViewEquatable を付けてみても結果は変わりませんでした。
Avatar
なるほどw
Avatar
omochimetaru 6/25/2020 7:07 AM
ビット比較してるんかなあ?
Avatar
ビット比較の可能性あるね。
Avatar
逆にそれしかなさそうな気がしてきた。参照型でもないし。
Avatar
omochimetaru 6/25/2020 7:08 AM
==はtrueになるけどビットは違う型と、 ビットは同じだけど==はfalseになる型を
7:08 AM
用意して実験する?
Avatar
AView(value: Bool.random() ? 1 : 2)
7:08 AM
↑これにすると更新されたりされなかったりでおもしろい。
😂 1
7:12 AM
import SwiftUI struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(value: Integer(42)) .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } BView(value: value) .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } final class Integer: Hashable, CustomStringConvertible { let value: Int init(_ value: Int) { self.value = value } static func == (lhs: Integer, rhs: Integer) -> Bool { lhs.value == rhs.value } func hash(into hasher: inout Hasher) { value.hash(into: &hasher) } var description: String { value.description } }
7:13 AM
↑これでも再描画されない。どうなってるんだ??
7:13 AM
参照型の場合はインスタンスのビット単位で比較してんのかな。アドレス関係なく。
Avatar
omochimetaru 6/25/2020 7:14 AM
参照型なら中身かもですね
Avatar
参照型の場合(だけかわからないけど) Equatable を見てるかも?
Avatar
omochimetaru 6/25/2020 7:15 AM
==の中身をtrueにしたら
7:16 AM
わかるかも
Avatar
final class Integer: Hashable, CustomStringConvertible { let value: Int init(_ value: Int) { self.value = value } static func == (lhs: Integer, rhs: Integer) -> Bool { true } func hash(into hasher: inout Hasher) { 42.hash(into: &hasher) } var description: String { value.description } } に変えて、 AView(value: Integer(Bool.random() ? 1 : 2)) にしたら更新されなくなった。
7:16 AM
Integer 変更前は AView(value: Integer(Bool.random() ? 1 : 2)) で更新された。
7:17 AM
参照型はビット比較するとアドレスが異なる場合に diff 発生してしまうから Equatable を見てる??
7:22 AM
import SwiftUI struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(value: Box<Double>(.nan)) .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } BView(value: value) .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } final class Box<Value>: Equatable, CustomStringConvertible where Value: Equatable, Value: CustomStringConvertible { let value: Value init(_ value: Value) { self.value = value } static func == (lhs: Box<Value>, rhs: Box<Value>) -> Bool { lhs.value == rhs.value } var description: String { value.description } } ↑これだと常に更新される。やっぱ == が見られてるっぽい。
7:23 AM
AView(value: Box<Double>(.nan))AView(value: Box<Double>(42)) に変更すると更新されなくなる。
Avatar
これってAViewとBViewをジェネリクスにしてるのは理由があるんですか?
Avatar
とりあえず Equatable な参照型は == が見られるのが確かみたい。
7:24 AM
@hironytic ジェネリックにしてるのは、いろんな型を渡して試しやすいからですね。
Avatar
なるほど。直接 Integer を持つようにしても同じなのかなあとちょっと思っただけです。
Avatar
AView(value: Box<Double>(.nan))AView(value: Double.nan) に変えるだけで参照型を値型にして試せるので。
7:25 AM
Double.nan のときは変更が反映されません。
7:27 AM
Equatable でないクラス Container ↓を作って final class Container<Value: CustomStringConvertible>: CustomStringConvertible { let value: Value init(_ value: Value) { self.value = value } var description: String { value.description } }
7:28 AM
これを渡した場合は、 AView(value: Container<Double>(42)) でも AView(value: Container<Double>(.nan)) でも常に更新が反映されました。
7:28 AM
同一性が見られてるっぽい。
Avatar
omochimetaru 6/25/2020 7:29 AM
いくつか挙動があるなあ・・・
7:30 AM
Doubleの場合: 1でもnanでも同じものを与えると更新されない Box<Double>の場合: インスタンス同一性を見ている EqBox<Double>の場合: Equatable.==を見ている iOS14でこっちで自前のコードで検証したけど同じ (edited)
Avatar
  • 値型→ビット比較
  • Equatable な参照型→ ==
  • Equatable でない参照型→アドレス比較(アドレスのビット比較)
7:30 AM
かなぁ。この挙動から考えると。
7:30 AM
iOS 14 ではどうかわからないけど・・・。
7:31 AM
Equatable な参照型だけ例外的と言えるかも。
Avatar
omochimetaru 6/25/2020 7:35 AM
参照型の場合にEquatableを調べるのであれば、値型のときもそうすればいいだろうに
7:35 AM
逆に値型の場合にビット比較しているなら、参照型の場合もそうすればいいのに・・・
7:36 AM
Avatar
参照型の場合にインスタンスをビット比較するならまだわかるんだけどね。
Avatar
omochimetaru 6/25/2020 7:39 AM
AG::LayoutDescriptor::compare_heap_objects(void const*, void const*, unsigned int, bool) AG::LayoutDescriptor::compare_indirect(AG::ValueLayout&, AG::swift::metadata const*, AG::swift::metadata const*, unsigned int, unsigned char const*, unsigned char const*) AG::LayoutDescriptor::compare_existential_values(AG::swift::existential_type_metadata const*, unsigned char const*, unsigned char const*, unsigned int)
7:40 AM
ジェネリックパラメータ型とExistential型の考慮があるなあ。
7:40 AM
#3 0x00007fff4a9b636e in AG::LayoutDescriptor::compare(unsigned char const*, unsigned char const*, unsigned char const*, unsigned long, unsigned int) () からジャンプしうるシンボル3種類↑
Avatar
Box<Double> をジェネリックでない Number ↓に変えても同じだった。 final class Number: Equatable, CustomStringConvertible { let value: Double init(_ value: Double) { self.value = value } static func == (lhs: Number, rhs: Number) -> Bool { print("Box.==") return lhs.value == rhs.value } var description: String { value.description } }
7:48 AM
ちょっとこれまでの話と関係ないですが、 SwiftUI で @Environment@EnvironmentObject 以外に、コンテクスト的に渡して使えるけど、イミュータブルなものを扱う方法って何かありますか?ダミーのイミュータブルな ObservableObject を作れば良い?
Avatar
@EnvironmentObject って型でマッチされるの?? import SwiftUI struct ContentView: View { var body: some View { SubView() .environmentObject(Foo(2)) .environmentObject(Bar(3)) .environmentObject(Bar(5)) } } struct SubView: View { @EnvironmentObject var foo: Foo @EnvironmentObject var bar1: Bar @EnvironmentObject var bar2: Bar var body: some View { VStack { Text("\(foo.value)") Text("\(bar1.value)") Text("\(bar2.value)") } } } final class Foo: ObservableObject { @Published var value: Int init(_ value: Int) { self.value = value } } final class Bar: ObservableObject { @Published var value: Int init(_ value: Int) { self.value = value } } ↑の実行結果は 2 3 3 (edited)
Avatar
Kishikawa Katsumi 6/25/2020 8:17 AM
それはたぶんそうじゃないかな。期待した結果と違いますか?
Avatar
あ、そういえば、同じ型のものが複数の箇所で指定されてたときは(上の例みたいに同じViewに複数modifierでつけるんじゃなくて、自分から親方向にViewを辿った時に指定が複数あるとき)、一番自分に近い側のものが選ばれるんだろうと勝手に期待していましたが、確認したことなかったです。
8:54 AM
import SwiftUI struct ContentView: View { var body: some View { SubView() .environmentObject(Foo(2)) } } struct SubView: View { @EnvironmentObject var foo: Foo var body: some View { VStack { SubsubView() .environmentObject(Foo(5)) Text("\(foo.value)") } } } struct SubsubView: View { @EnvironmentObject var foo: Foo var body: some View { VStack { Text("\(foo.value)") } } } final class Foo: ObservableObject { @Published var value: Int init(_ value: Int) { self.value = value } }
8:54 AM
結果 5 2 期待通り
Avatar
omochimetaru 6/25/2020 8:55 AM
なるほど。
Avatar
ReactのContext、FlutterでいうところのInheritedWidget(…を使って提供されているProvider)に相当するやつですね。
9:02 AM
しばらくReact、Flutterをやってなかったから名前が出てこなくていま検索しまくってたのはひみつです 🤫
😮 2
Avatar
あ、そういえば、同じ型のものが複数の箇所で指定されてたときは(上の例みたいに同じViewに複数modifierでつけるんじゃなくて、自分から親方向にViewを辿った時に指定が複数あるとき)、一番自分に近い側のものが選ばれるんだろうと勝手に期待していましたが、確認したことなかったです。
二つぶら下げた場合は内側の方が近いと考えれば一貫している?最初に見つかるものを返していると。
9:32 AM
それはたぶんそうじゃないかな。期待した結果と違いますか?
なんか、キー(文字列とか)を指定して引くとかならわかるんですが、型でマッチってちょっと斬新な気がして。同じ型の値を複数付けられないですし。
Avatar
ラップした型を都度作れば良さそう
Avatar
うーん、できるできないというよりも、なんか斬新な設計だなと。
Avatar
omochimetaru 6/25/2020 9:36 AM
文字列はうっかり衝突したりtypoする可能性があるけど、型だったら、そういう心配はないですね
Avatar
DIコンテナってそもそもこんな感じの気がするんですが >型ベースの設計
Avatar
omochimetaru 6/25/2020 9:37 AM
DIコンテナだとわりとよくある気もする
Avatar
うーん、なんというか、( @EnvironmentObject にできるかは一度おいておいて、) NSNumber 二つは紐付けられないけど IntDouble ならできるとか、型でマッチするのって違和感があるというか。
9:58 AM
文字列はうっかり衝突したりtypoする可能性があるけど、型だったら、そういう心配はないですね
安全性に関しては、 @EnvironmentObject は紐付け忘れたらクラッシュだから、そもそも安全なわけじゃないからなぁ。ミスする箇所が減るとは言っても。
Avatar
omochimetaru 6/25/2020 9:58 AM
そういう汎用のデータ型を使うのではなくて、注入する依存性を現した型を専用に定義する感じだと思います
9:59 AM
紐付け忘れはそうですね。 それが気になる場合はObservableObjectをinitで渡す方式の方が良いと思う。 いわゆるコンストラクタDIと言えると思う。
10:01 AM
ただViewがinitした時点で多分チェックされる?から、動的バグとはいっても一度動かせば検出できるたぐいのマシなやつだとも思う (edited)
10:01 AM
ナビゲーションが深くて到達しづらい画面だと見落とすとかはあるかな。
Avatar
その @EnvironmentObject を使う箇所を通過しないとクラッシュしないことないかな?たとえば、ボタン押したクロージャの中から参照してるとか。
10:03 AM
@ObservedObject との比較で言えば、 @EnvironmentObject って @ObservedObject を都度渡してチェーンするのの簡略版だと思うんだけど、 @ObservedObject なら同じ型のものを複数持てるけど、 @EnvironmentObject だとそれができないというところに、「簡略版」以上の不必要な意味を持ってしまっているというか・・・。
Avatar
通過しないとクラッシュしないこと
まああると思います。
10:04 AM
例えばアプリ全体の文字の色を変えるような機能を導入したかったとして
10:04 AM
そのための色のプロパティとか、Style型みたいなものを、全部のビューツリーのinitで伝搬していくのは大変だから
10:05 AM
サボりたいときに使うとかが良いんじゃないか
Avatar
↑の例でいうと、二種類の色を @EnvironmentObject で管理しようとすると破綻しない?
10:05 AM
特別な型を作ればもちろんできるんだけど。
Avatar
Style型を作るイメージですね。そこにいろんな色のプロパティをもたせる。
10:06 AM
class Style: EnvironmentObject { var textColor: Color var fontSize: Int } (edited)
Avatar
いや、当然今の @EnvironmentObject を前提とするとそういう使い方になるんだけど、
10:07 AM
それって API 仕様に引きづられた判断じゃないかな?
10:07 AM
もし identifier 渡す設計になってたらばらばらに紐付けるんじゃない?
Avatar
どうしてもやりたければEnvironmentObjectKeyみたいな仕組みなかったでしたっけ
10:08 AM
去年気になって調べた気がする
Avatar
えっと、やりたい、やりたくないの話じゃなくて、
10:08 AM
@EnvironmentObject が型でマッチする設計になっていることに違和感を感じているという話です。
Avatar
API 仕様に引きづられた判断
実際にStyle型みたいなものは普段から作ってますね
10:09 AM
僕は動的エラーは嫌いなのでコンストラクタDIに寄せることが多いけど
10:10 AM
複数の色情報を管理する事になる場合が多くて、
10:10 AM
そのような場合に伝搬させていく箇所n x パラメータ数m で作業が大変になっちゃうから
10:10 AM
n x 1 にしたいから、型でひとまとめにすることになる
10:11 AM
型じゃないキーにする方法もあったかもしれないというのと、
10:11 AM
絶対に値は与えることになるから、型がキーにできるようになっていると、
10:12 AM
記述量が短くできるからデフォルトの振る舞いとしては便利なんじゃないかしら。
10:12 AM
Identifierの仕組みだと、値とセットで、渡す側と使う側でそれを都度指定しないといけない
Avatar
color scheme 的なものは一つの型にまとめるだろうけど、無関係な二つの色だとばらばらに紐付けたくならないかなぁ。
Avatar
そういうことはありますね
10:15 AM
2つの全く異なるGUI部品ライブラリが混在していて (edited)
10:16 AM
それぞれが別のスタイルセットを要求しているとか。
10:17 AM
同じセットでも、そのStyleを要求してるけど使わないプロパティがあるとかは起きるので
10:17 AM
関心の分離ができてない状態になっちゃいがち
10:18 AM
かつ、そうなると別プロジェクトでの部品単体の再利用性が悪い
10:20 AM
ただ自分は、画面デザインというものが本質的に部品単体であんまり機能しないもので、 ボタンとかテキストとかいろんな要素が横断的に統一感を持つことで成立すると思うから、 どうせ再利用性が低いのでいいかなと思ってる
Avatar
うーん、まあ EnvironmentKey の便利版みたいな位置づけだといいのかなぁ・・・。
Avatar
↑の例でいうと、二種類の色を @EnvironmentObject で管理しようとすると破綻しない?
そういうのは、 @Environment でやることを想定してるんじゃないかなあ。
Avatar
そうですねぇ。 @EnvironmentObject を使う場合は、型でマッチすることを割り切って使う場合って感じでしょうか・・・。
2:22 AM
@Environment だとデフォルト値があるので、付与し忘れてクラッシュがないという違いはありそうですね。
Avatar
えー・・・。昨日の親の変更が子に伝播する件、 ListNavigationLink 使ってるときに、子側をトリガーとして変更が発生したときに、親の List が再描画されると子が pop されて親 View まで戻される現象が発生した・・・。これは許容し難い。どういうケースで起こるのか最小構成作って試してみないと・・・。
9:16 AM
NavigationLink の発行元が消滅したから?
Avatar
omochimetaru 6/26/2020 9:16 AM
元々NavigationLinkを持っていた子が親のListから消えたら巻き戻るのでは?
9:17 AM
昨日の話だと、それでiOS14は一貫してる
Avatar
(実際には消滅してなくて順序が入れ替わってるだけなんだけど)
Avatar
omochimetaru 6/26/2020 9:17 AM
じゃあ子の同一性判定が失敗した? Identifiable になってますか?
Avatar
なってます。
Avatar
omochimetaru 6/26/2020 9:17 AM
それはキツイですね。
Avatar
閲覧履歴を List で表示してて、アイテムを選択してアイテムページに遷移して、そこで閲覧ボタンを押すと履歴が書き換わって pop される・・・。
9:20 AM
NavigationLink の同一性みたいなのがあるのかな・・・。
Avatar
omochimetaru 6/26/2020 9:20 AM
List側が持ってる<C: Collection> のIdentifiableで紐づくと期待してた
Avatar
うん、そうあってほしいよね。
Avatar
omochimetaru 6/26/2020 9:21 AM
ListのRowの子とNavigationLinkの繋がりはよくわかんないけど1番上で包んでるなら紐付いてほしいし。
Avatar
履歴の書き換えは削除と末尾挿入を行ったあとで一回だけ PassthroughSubject 経由で更新通知を発火してるから、入れ替えてる途中に消えてる瞬間もないはずなんだよなぁ。
9:22 AM
とりあえず body が一度しか発火されてないことを確認してみるか。
Avatar
omochimetaru 6/26/2020 9:23 AM
ああ、閲覧履歴だから閲覧したことによって上に来るんですね。
Avatar
はい、そうなんです。やっぱ一度しか発火してないなぁ。
Avatar
omochimetaru 6/26/2020 9:25 AM
iOS14で実行してますか?
Avatar
iOS13です。
Avatar
omochimetaru 6/26/2020 9:29 AM
14なら動くかも
Avatar
とりあえず、 NavigationLink 使わずに UINavigationController 経由で push させれば回避できた。
9:30 AM
こんなこともあろうかと、根っこを NavigationView にせずに UINavigationController にしておいて、カスタム @Environment に渡してある。
Avatar
NavigationLink 鬼門ですね・・・ (edited)
Avatar
僕の使い方が良くないかもしれないし、実プロジェクトは色々絡んでて複雑なので、最小構成で再現するか試したいですね。
9:32 AM
iOS 14 でも試せるように早く beta 落とさないと。
Avatar
NavigationLinkisActive: のBindingをとる版とか、 tag:selection: 版とかだとどれが開いているかっていうStateがこっちが与えたBindingに保持されますが、ないやつはどこが覚えてるんでしょうね。
9:33 AM
「開いているか」というか、どれがpushされているか(isActive に用語を合わせるなら、どれがアクティブか) (edited)
Avatar
omochimetaru 6/26/2020 9:34 AM
NavigationViewがパスとして覚えているのかなと想像してた
Avatar
でも NavigationLink ってそれ自体にはID渡さないですよね。
Avatar
omochimetaru 6/26/2020 9:35 AM
なので構造に応じて勝手に判断しているのかと思ってた
9:36 AM
Listに包まれてたらそのIdentity
Avatar
ということは、それが単にView階層のパスで判断してたら、位置が変わるだけでアウトになりそうなのでそれかも。
Avatar
omochimetaru 6/26/2020 9:37 AM
そうですね、IDとかKeyみたいなものじゃなくて、Viewツリーパスだったら、ダメそうですね
9:37 AM
いや、位置だけだったら、別エントリって事になるのかな
9:38 AM
そこでdiffを見るとか・・・?
Avatar
普通にVStackの中にNavigationLinkを3つ並べたときとかは、VStackの何番目の子かしか判断できませんからね。
Avatar
omochimetaru 6/26/2020 9:38 AM
再描画されてその3つの要素が入れ替わってても、入れ替わったのか中身が変化したのか、判断できないし謎だ
Avatar
????再描画が走っても、順番が入れ替わらなかったら pop されない??
9:42 AM
最新履歴のアイテムを閲覧しても、 body はたしかに走ってるんだけど、 pop されない。
Avatar
Viewツリーのパス説が濃厚になってきました?
Avatar
うーん、でも何で判定してるんでしょう?
9:43 AM
三つ履歴があるときに、最新のを閲覧しても、一番古いのを閲覧しても、結局アイテムは三つのままだから、 (edited)
Avatar
そうか。子の位置だけなら、popされずに別の子になりそうだし。
Avatar
順番が入れ替わったかどうかって Identifiableid でも見ないと判定できなくないですか?
9:44 AM
そこまでしてるなら id 同じ NavigationLink があれば pop しないでほしいけど・・・。
Avatar
ふと思ったんですが、あるViewに @State で持たせた変数って、そのViewが List の中に入ってて、そのリスト上の位置が変わったときって、状態は保持されてるんですかね。
9:49 AM
Viewが @State で状態を持っていて、その親のViewの関係ないところに更新が走ったとしても、 @State で持っている状態は保持されないと困りますよね。でも、親のViewの body は呼ばれるはずだから、子Viewだってイニシャライザが走って別物になっているはず。でも、 @State はどこかで同一性を判定して覚えてくれてるはず。
9:52 AM
これは試してみたくなったけど、これから用事があるので残念。またあとでやってみます。
Avatar
List じゃないケースだと、イニシャライザが走っても @State が保持されてるのは確認したことがあります。
Avatar
import SwiftUI import Foundation import Combine struct ContentView: View { @ObservedObject var history: History = .shared let dateFormatter: DateFormatter = { let value = DateFormatter() value.dateFormat = "yyyy-MM-dd HH:mm:ss" return value }() let items: [Item] = [ Item(id: "A"), Item(id: "B"), Item(id: "C"), ] var body: some View { NavigationView { VStack { VStack { ForEach(items) { item in NavigationLink(destination: ItemView(item: item)) { Text("Open \(item.id)") .padding(4) } } } .padding() Text("履歴") .font(.headline) .padding() Divider() List(history.records.reversed()) { record in NavigationLink(destination: ItemView( item: self.items.first(where: { $0.id == record.id })! )) { VStack(alignment: .leading) { Text("\(record.id)") .font(.headline) Text("\(self.dateFormatter.string(from: record.lastPlayed))") .font(.subheadline) .foregroundColor(.secondary) } } } } } } } struct ItemView: View { let item: Item var body: some View { VStack { Text("\(item.id)") Button("Play") { History.shared.record(for: self.item.id) } } } }
10:33 AM
struct Item: Identifiable { let id: String } final class History: ObservableObject { private(set) var records: [Record] = [] private let subject: PassthroughSubject<Void, Never> = .init() var objectWillChange: AnyPublisher<Void, Never> { subject.eraseToAnyPublisher() } func record(for id: Item.ID) { records.removeAll(where: { $0.id == id }) records.append(Record(id: id, lastPlayed: Date())) subject.send(()) } static let shared: History = .init() struct Record: Identifiable { let id: Item.ID let lastPlayed: Date } }
10:34 AM
↑これで再現しました。
10:34 AM
A, B, C の順に "Open" して "Play" してから履歴から A を選んで "Play" すると pop される。
10:37 AM
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes.
こんな実行時警告出てるけど、そもそも子のアクションで親を再レンダリングしてほしくないなぁ・・・。親に戻ったときに走るならいいけど。
Avatar
Xcode 12 beta 落としたけど同じだな。昨日の実験から考えるとあたりまえだけど。
11:31 AM
[UIContextMenuInteraction] Attempting -[UIContextMenuInteraction dismissMenu], when not in an active state. This is a client error most often caused by calling dismiss more than once during a given lifecycle.
実行時の警告は変わった・・・。
Avatar
struct ContentView: View { var body: some View { NavigationView { FirstView() } .environmentObject(Env(items: [ Item(id: "C"), Item(id: "B"), Item(id: "A"), ])) } } struct FirstView: View { @EnvironmentObject var env: Env var body: some View { List(env.items) { item in NavigationLink(destination: SecondView(id: item.id)) { Text("\(item.id)") } } } } struct SecondView: View { @EnvironmentObject var env: Env let id: String init(id: String) { self.id = id } var body: some View { Button("\(id)") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } } } struct Item: Identifiable { let id: String } final class Env: ObservableObject { @Published var items: [Item] init(items: [Item]) { self.items = items } } ↑だいぶ単純化しましたが再現しますね。(Aの行をタップして、遷移先でAをタップ)
Avatar
struct ContentView: View { var body: some View { NavigationView { FirstView() } .environmentObject(Env(items: [ Item(id: "C"), Item(id: "B"), Item(id: "A"), ])) } } struct FirstView: View { @EnvironmentObject var env: Env var body: some View { VStack { Button("move") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } List(env.items) { item in ItemView(id: item.id) } } } } struct ItemView: View { let id: String @State var flag: Bool = false var body: some View { HStack { Text("\(id)") Button(flag ? "true" : "false") { self.flag.toggle() } } } } struct Item: Identifiable { let id: String } final class Env: ObservableObject { @Published var items: [Item] init(items: [Item]) { self.items = items } } ↑ぼくの疑問だった @State は並び替えてもちゃんと保たれている様子。(falseの部分をタップするとtrueとfalseがトグルするので適当に変更してからmoveを押して並び替えてもtrue, falseは保たれて並び変わる)
12:20 PM
import SwiftUI struct ContentView: View { var body: some View { NavigationView { FirstView() } .environmentObject(Env(items: [ Item(id: "C"), Item(id: "B"), Item(id: "A"), ])) } } struct FirstView: View { @EnvironmentObject var env: Env var body: some View { VStack { Button("move") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } List(env.items) { item in ItemView(id: item.id) } } } } struct ItemView: View { let id: String @State var flag: Bool = false var body: some View { NavigationLink(destination: SecondView(id: id)) { HStack { Text("\(id)") Text(flag ? "true" : "false") .onTapGesture { self.flag.toggle() } } } } } struct SecondView: View { @EnvironmentObject var env: Env let id: String init(id: String) { self.id = id } var body: some View { Button("\(id)") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } } } struct Item: Identifiable { let id: String }
12:20 PM
↑うん? NavigationLink で遷移して強制的にpopされるパターンだと @State で覚えてるやつもクリアされる。どうも完全にリセットされてるっぽいですね。 (edited)
12:21 PM
(falseの部分をタップするとtrueとfalseがトグルするので適当に変更してから、Aの行をタップして次画面へ遷移したのち、Aをタップしてpopで戻されると、全部falseになってる)
12:24 PM
この例、一度再現したあとに再度同じ操作を続けて行っても、最後に並び替わらないのでpopされないんだけど、そのときBackで戻ってもちゃんと true, false は残ってる。たぶん実際は因果関係が逆で @State で覚えてるようなものがリセットされるからpopされるんだとは思いますが。
Avatar
あれ? FirstViewList のところを、 List { ForEach(env.items) { item in ItemView(id: item.id) } } とすると、いったんpopされるけどすぐにpushされてtrue、falseの状態も保たれてます。 ListForEach の違いはなに? 😓
12:36 PM
この List を消すと(つまり ForEach だけにすると)、TableView表示はされなくなるんですが、AとかBとかの部分を押すと画面遷移はします。ただ、そうすると強制popは起こらなくなります。
12:37 PM
ListNavigationLink の組み合わせでこの現象は起こってるぽいですね。
12:43 PM
https://nalexn.github.io/swiftui-deep-linking/ ↑には ListUITableView と同じで lazily に動作して見えてない部分は作らない機構があって、見えてないとプログラムから遷移させられないという問題が書かれているんですが、今回のもそのlazilyなための仕組みが関係あるかもしれませんね。
Deep Links, Notifications, Quick Actions, Shortcuts and more
Avatar
List が表示されていない間は流れをせき止める ObservableObject でラップすることで、遷移先からの更新の反映を遅らせることができました。 final class ObservableDam<WrappedValue: ObservableObject>: ObservableObject { typealias ObjectWillChangePublisher = AnyPublisher<WrappedValue.ObjectWillChangePublisher.Output, Never> let wrappedValue: WrappedValue private var cancellable: AnyCancellable? = nil var isActive: Bool = true { didSet { if isActive { let outputs = self.outputs self.outputs = [] for output in outputs { subject.send(output) } } } } private var outputs: [ObjectWillChangePublisher.Output] = [] private let subject: PassthroughSubject<ObjectWillChangePublisher.Output, Never> = .init() var objectWillChange: ObjectWillChangePublisher { subject.eraseToAnyPublisher() } init(_ wrappedValue: WrappedValue) { self.wrappedValue = wrappedValue cancellable = wrappedValue.objectWillChange.sink { [weak self] output in guard let self = self else { return } if self.isActive { self.subject.send(output) } else { self.outputs.append(output) } } } }
2:39 PM
↑こういうのを作って、 struct ContentView: View { @ObservedObject var history: ObservableDam<History> = .init(.shared) ↑こうして } .onAppear { self.history.isActive = true } .onDisappear { self.history.isActive = false } ↑こうする。
2:40 PM
戻ってきた瞬間に更新が発火して、アニメーションして最新状態が表示される。 (edited)
Avatar
NavigationLink についての質問を Apple の Forum に投稿したらエラーになって質問全部消えた😂
😢 2
Avatar
とりあえず忘れないうちに書き出してみた。 https://gist.github.com/koher/2b7b6a7b172dbd0585aa754f0ef0a0a7
Avatar
UITableViewの警告が出ていることからも、 List は内部的には UITableView を使って実現されていて、裏で、 UITableViewreloadData() なり beginUpdates()endUpdates() をしてるんじゃないかと推測。
7:34 AM
しかし、画面に見えてない時にそれをするから、UITableView was told to layout its visible cells and other contents without being in the view hierarchyと警告が出て、そして、たぶんレイアウト結果の見えてるセルだけが仮想Viewに作られるところが、見えてないので何も作られなくなって、その結果、 NavigationLink も存在しなくなって戻るのではないかと思いました。
Avatar
↑現象をまとめておきました。 https://qiita.com/hironytic/items/babb07b817e67499174e
Discordの swift-developers-japanサーバー で興味深い話題があったので、気になって調べてみたものを雑にまとめました (話題自体は このへん から) 特に明記していない限り、Xcode 11.5とシミュレ...
🙏 1
Avatar
これiOS 14からの LazyVStack ではどうなるのかがちょっと気にはなってます。前画面でスクロール範囲から見えなくなったらpopされたりすることがあるのだと嫌だなあと。気になってるだけで試してませんが。
Avatar
そして、たぶんレイアウト結果の見えてるセルだけが仮想Viewに作られるところが、見えてないので何も作られなくなって、その結果、 NavigationLink も存在しなくなって戻るのではないかと思いました。
履歴リストの例で、最新の項目を play した場合(つまり、セルの順番は変わらないけど body は走った場合)は pop されないんですよね。これは、仮想ビューツリーの diff を取った結果、変化がないから生き続けてるってことですかね?で、変化があるとレンダリングも必要だから一度破棄されて、表示されてないからレンダリングが行われないから消えてしまう?
9:09 AM
とりあえず NavigationLink 使わずに navigationController を取り出して push したら回避はできるんですが、今度は List のセルに disclosure indicator を出すのが NavigationLink の有無と連動してたり、セル選択のときの UI の挙動(セルの背景がグレーになるとか)が異なってたりでちょっと微妙です・・・。
9:11 AM
今のところ↑に書いた通知を一時的にせき止める ObservableObject をかまそうかと思ってますが、 iOS 14 だと N 階層でも伝わるので、事故を回避するためには List 以下で変更される可能性のあるもの全部に付けないといけなくてかなり面倒です・・・。
9:13 AM
これ、今 static var shared: History だからトリッキーなことに見えますけど、普通に Core Data で @FetchRequest 使ってる場合とか同じじゃないかと思うんですよねぇ・・・。
Avatar
そうですよね。サーバーサイドにCloud Firestoreのようなリアルタイム性があっても普通に起こると思うし、ローカルでもiPadのMultiple Windowsのように片方の画面でデータを変更したのが他方にも伝わるというのもあり得るんで、割と普通に起こることだと思うんですよね 🙁
Avatar
Core Data と CloudKit Database の連携機能とかもありますしねぇ・・・。 https://developer.apple.com/videos/play/wwdc2019/202/ https://developer.apple.com/videos/play/wwdc2020/10650/
CloudKit offers powerful, cloud-syncing technology while Core Data provides extensive data modeling and persistence APIs. Learn about...
Discover how Core Data can help you adopt the CloudKit public database in your app with as little as one line of code. Learn how to...
Avatar
質問させてください。 iOSのGmail Appのような挙動をするリストを実装したいです。 実装方法を教えて頂きたいです。
3:36 AM
このような感じで左右スライドできるものにしたいです。
3:36 AM
gestureを使って左右スライドできる viewをscroll viewを用いてリストを作成することはできました。 (edited)
3:37 AM
しかしそれではスクロールができなくなってしまいました。
3:39 AM
説明下手ですみません。よろしくお願いします。
Avatar
自分で左右スライドだけ実装したものです↓ https://github.com/2357or/DismissibleList/blob/master/DismissibleList/DismissibleCard.swift (edited)
Contribute to 2357or/DismissibleList development by creating an account on GitHub.
Avatar
(自分の能力不足により)まったく質問にお答えすることができなくて申し訳ないのですが、質問と関係ない点で、 https://github.com/2357or/DismissibleList/blob/master/DismissibleList/DismissibleCard.swift#L10 の幅を取得する部分は、スクリーンのサイズじゃなくて GeometryReader を使う方が親のサイズに合わせることができていいのかなあと思いました。たぶん、そうしたからといって質問の件はぜんぜん解決しないとは思いますが 💦
Contribute to 2357or/DismissibleList development by creating an account on GitHub.
👍 1
Avatar
このstackoverflowは同じ内容に見えますね、ここに書いてあるアプローチは全て試しましたか? https://stackoverflow.com/questions/57700396/adding-a-drag-gesture-in-swiftui-to-a-view-inside-a-scrollview-blocks-the-scroll
So I have a ScrollView holding a set of views: ScrollView { ForEach(cities) { city in NavigationLink(destination: ...) { CityRow(city: city) } ...
👍 1
Avatar
@hironytic ありがとうございます。 実際にアプリに組み込む時はGeometryRenderを使おうと思います。 (GeometryRender使うとコードのネストが深くなって実験用には面倒だなと思ったのでスクリーンから取ってました。)
8:34 AM
@tarunon 助かりました!ありがとうございます! stackoverflowにある方法を試してみようと思います。 申し訳ないのですが、自分は高校生でして親にパソコン使用の制限をかけられているので、結果報告が遅くなるかもしれません。すみません。 (edited)
🥳 2
Avatar
できました!!ありがとうございました! タップジェスチャーを追加することでスクロールが可能になりました。 DragGesture()にminimumDistance を追加しても同様にスクロールできました。 (edited)
🎉 7