diff --git a/Tusker/Screens/Compose/ComposeReplyContentView.swift b/Tusker/Screens/Compose/ComposeReplyContentView.swift index 48d488ec..28df101e 100644 --- a/Tusker/Screens/Compose/ComposeReplyContentView.swift +++ b/Tusker/Screens/Compose/ComposeReplyContentView.swift @@ -23,6 +23,7 @@ struct ComposeReplyContentView: UIViewRepresentable { view.setTextFrom(status: status) view.isUserInteractionEnabled = false view.backgroundColor = .clear + view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) return view } diff --git a/Tusker/Screens/Compose/ComposeReplyView.swift b/Tusker/Screens/Compose/ComposeReplyView.swift index 8c984ef5..601243f2 100644 --- a/Tusker/Screens/Compose/ComposeReplyView.swift +++ b/Tusker/Screens/Compose/ComposeReplyView.swift @@ -11,8 +11,8 @@ import SwiftUI struct ComposeReplyView: View { let status: StatusMO let stackPadding: CGFloat - let outerMinY: CGFloat + @State private var displayNameHeight: CGFloat? @State private var contentHeight: CGFloat? @ObservedObject private var preferences = Preferences.shared @@ -37,23 +37,25 @@ struct ComposeReplyView: View { Spacer() } - - ComposeReplyContentView(status: status) { (newHeight) in - self.contentHeight = newHeight + .background(GeometryReader { proxy in + Color.clear + .preference(key: DisplayNameHeightPrefKey.self, value: proxy.size.height) + .onPreferenceChange(DisplayNameHeightPrefKey.self) { newValue in + displayNameHeight = newValue + } + }) + + ComposeReplyContentView(status: status) { newHeight in + contentHeight = newHeight } - .offset(x: -4, y: -8) - .padding(.bottom, -8) + .frame(height: contentHeight ?? 0) } - .frame(height: max(50, contentHeight ?? 0) + 8) } - .padding(.bottom, -8) } private func replyAvatarImage(geometry: GeometryProxy) -> some View { - // using named coordinate spaces produces an incorrect scroll offset on iOS 13, - // so simply compare the geometry inside and outside the scroll view in the global coordinate space - let scrollOffset = outerMinY - geometry.frame(in: .global).minY - + let scrollOffset = -geometry.frame(in: .named(ComposeView.coordinateSpaceOutsideOfScrollView)).minY + // add stackPadding so that the image is always at least stackPadding away from the top var offset = scrollOffset + stackPadding @@ -61,7 +63,7 @@ struct ComposeReplyView: View { offset = max(offset, 0) // subtract 50, because we care about where the bottom of the view is but the offset is relative to the top of the view - let maxOffset = (contentHeight ?? 0) - 50 + let maxOffset = (contentHeight ?? 0) + (displayNameHeight ?? 0) - 50 // once you scroll past the in-reply-to-content, the bottom of the avatar should be pinned to the bottom of the content offset = min(offset, maxOffset) @@ -74,6 +76,13 @@ struct ComposeReplyView: View { } +private struct DisplayNameHeightPrefKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + //struct ComposeReplyView_Previews: PreviewProvider { // static var previews: some View { // ComposeReplyView() diff --git a/Tusker/Screens/Compose/ComposeView.swift b/Tusker/Screens/Compose/ComposeView.swift index 8ee358fc..3470d9de 100644 --- a/Tusker/Screens/Compose/ComposeView.swift +++ b/Tusker/Screens/Compose/ComposeView.swift @@ -42,6 +42,8 @@ import Combine } struct ComposeView: View { + static let coordinateSpaceOutsideOfScrollView = "coordinateSpaceOutsideOfScrollView" + @ObservedObject var draft: Draft @EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var uiState: ComposeUIState @@ -77,20 +79,12 @@ struct ComposeView: View { } var body: some View { - mostOfTheBody.toolbar { - ToolbarItem(placement: .cancellationAction) { cancelButton } - ToolbarItem(placement: .confirmationAction) { postButton } - } - } - - var mostOfTheBody: some View { ZStack(alignment: .top) { - GeometryReader { (outer) in - ScrollView(.vertical) { - mainStack(outerMinY: outer.frame(in: .global).minY) - } - .scrollDismissesKeyboardInteractivelyIfAvailable() + ScrollView(.vertical) { + mainStack } + .coordinateSpace(name: ComposeView.coordinateSpaceOutsideOfScrollView) + .scrollDismissesKeyboardInteractivelyIfAvailable() if let poster = poster { // can't use SwiftUI.ProgressView because there's no UIProgressView.Style.bar equivalent, see FB8587149 @@ -108,6 +102,10 @@ struct ComposeView: View { dismissButton: .default(Text("OK")) ) } + .toolbar { + ToolbarItem(placement: .cancellationAction) { cancelButton } + ToolbarItem(placement: .confirmationAction) { postButton } + } } @ViewBuilder @@ -122,14 +120,13 @@ struct ComposeView: View { .animation(.default, value: uiState.autocompleteState) } - func mainStack(outerMinY: CGFloat) -> some View { + var mainStack: some View { VStack(alignment: .leading, spacing: 8) { if let id = draft.inReplyToID, let status = mastodonController.persistentContainer.status(for: id) { ComposeReplyView( status: status, - stackPadding: stackPadding, - outerMinY: outerMinY + stackPadding: stackPadding ) }