diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift index 64b94f3d..488396fa 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift @@ -116,6 +116,8 @@ public struct NavigationTitlePreferenceKey: PreferenceKey { private struct ToolbarActions: ToolbarContent { @ObservedObject var draft: Draft + // Prior to iOS 16, the toolbar content doesn't seem to have access + // to the environment form the containing view. let controller: ComposeController var body: some ToolbarContent { diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift index 65343477..3286ab77 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift @@ -8,6 +8,8 @@ import SwiftUI struct NewMainTextView: View { + static var minHeight: CGFloat { 150 } + @Binding var value: String @FocusState.Binding var focusedField: FocusableField? @State private var becomeFirstResponder = true @@ -16,6 +18,7 @@ struct NewMainTextView: View { NewMainTextViewRepresentable(value: $value, becomeFirstResponder: $becomeFirstResponder) .focused($focusedField, equals: .body) .modifier(FocusedInputModifier()) + .modifier(HeightExpandingModifier(minHeight: Self.minHeight)) .overlay(alignment: .topLeading) { if value.isEmpty { PlaceholderView() @@ -34,6 +37,9 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { @Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard // TODO: test textSelectionStartsAtBeginning @Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning + #if !os(visionOS) + @Environment(\.textViewContentHeight) @Binding private var textViewContentHeight + #endif func makeUIView(context: Context) -> UITextView { let view: UITextView @@ -86,6 +92,16 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { becomeFirstResponder = false } } + + #if !os(visionOS) + if #unavailable(iOS 16.0) { + DispatchQueue.main.async { + let targetSize = CGSize(width: uiView.bounds.width, height: UIView.layoutFittingCompressedSize.height) + let fittingSize = uiView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultHigh) + textViewContentHeight = fittingSize.height + } + } + #endif } func makeCoordinator() -> WrappedTextViewCoordinator { @@ -96,12 +112,11 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { return coordinator } - // TODO: fallback for this on iOS 15 @available(iOS 16.0, *) func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIViewType, context: Context) -> CGSize? { let width = proposal.width ?? 10 let size = uiView.sizeThatFits(CGSize(width: width, height: 0)) - return CGSize(width: width, height: max(150, size.height)) + return CGSize(width: width, height: max(NewMainTextView.minHeight, size.height)) } } @@ -261,3 +276,38 @@ private struct PlaceholderView: View { .allowsHitTesting(false) } } + +#if !os(visionOS) +@available(iOS, obsoleted: 16.0) +private struct HeightExpandingModifier: ViewModifier { + let minHeight: CGFloat + + @State private var height: CGFloat? + private var effectiveHeight: CGFloat { + height.map { max($0, minHeight) } ?? minHeight + } + + func body(content: Content) -> some View { + if #available(iOS 16.0, *) { + content + } else { + content + .frame(height: effectiveHeight) + .environment(\.textViewContentHeight, $height) + } + } +} + +@available(iOS, obsoleted: 16.0) +private struct TextViewContentHeightKey: EnvironmentKey { + static var defaultValue: Binding { .constant(nil) } +} + +@available(iOS, obsoleted: 16.0) +private extension EnvironmentValues { + var textViewContentHeight: Binding { + get { self[TextViewContentHeightKey.self] } + set { self[TextViewContentHeightKey.self] = newValue } + } +} +#endif