SwiftUIのpopover、なかなか手強いですね。
iPadで「できた!」と思ってもiPhoneでポップオーバーじゃなくてシートになったり。
これはiOS 16.4+ではpopoverのコンテントにpresentationCompactAdaptation(.none)修飾子をつけることで解決します。
さて、長いテキストをポップオーバーさせようとすると行数が3行程度しか表示されません。
struct ContentView: View { @State private var isPopoverOpen = false var body: some View { Spacer() Text("Hello, World!") .popover(isPresented: $isPopoverOpen) { Text(""" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ ) .fixedSize(horizontal: false, vertical: true) .padding() .presentationCompactAdaptation(.none) } } }
iOS16から利用可能になったLayoutプロトコルによると、SwiftUIではsizeThatFitsメソッドを通じてサイズに関して親ビューと交渉して、交渉後のサイズで子ビューをレイアウトするという仕組みらしいです。
popoverは何かの理由でこの交渉に失敗してそうです。
ではちゃんと交渉するLayoutを作りましょう。
struct PopoverContainer: Layout { func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { guard subviews.count == 1 else { fatalError("You need to implement your layout!") // 用途がレイアウトではなく交渉なので子ビューは1つに限定しています } var p = proposal // 提案がnil(自由にしてよい)の場合もスクリーンサイズの制約があるのでそれを採用する。 p.width = p.width ?? UIScreen.main.bounds.width p.height = p.height ?? UIScreen.main.bounds.height return subviews[0].sizeThatFits(p) // negotiates possible size } func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { // 何もしないと子ビューをoriginに単に置く動作のようです } }
このコンテナでpopoverのコンテントをラップします。
struct ContentView: View { @State private var isPopoverOpen = false var body: some View { Spacer() Text("Hello, World!") .popover(isPresented: $isPopoverOpen) { PopoverContainer { Text(""" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ ) .fixedSize(horizontal: false, vertical: true) .padding() } .presentationCompactAdaptation(.none) } } }
うーん、微妙にはみ出ました。
どうやら、スクリーンサイズからの希望では、popover側のパディング/マージンが足らず最終交渉で値切られているようです。
交渉時にpopoverに必要そうな余裕を残してみましょう。
(popover側にちゃんと最初から交渉しろよと言いたいですが…)
struct PopoverContainer: Layout { func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { let popoverPadding: CGFloat = 40 // これがpopover側にあげる余裕 guard subviews.count == 1 else { fatalError("You need to implement your layout!") } print(#function, proposal) var p = proposal p.width = p.width ?? (UIScreen.main.bounds.width - popoverPadding) p.height = p.height ?? (UIScreen.main.bounds.height - popoverPadding) print(p, subviews[0].sizeThatFits(p)) return subviews[0].sizeThatFits(p) // negotiates possible size } func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { print(#function, proposal) // entrusts default } }
結果。
popoverPaddingがハードコードなのは気に入りませんが、とりあえずうまく行きました。
Enjoy programming!