Fix very bad performance when laying out Compose reply view

Using a non-scrolling UITextView wrapped in SwiftUI combined with the
old hack of fixing its layout by passing the view controller's width
down to the wrapped view caused very slow layouts, resulting in
significant lag when typing into the main text view of the compose
screen.
This commit is contained in:
Shadowfacts 2020-09-06 22:47:02 -04:00
parent 9dce94c014
commit 262aadf807
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 16 additions and 40 deletions

View File

@ -11,24 +11,18 @@ import Combine
struct ComposeContainerView: View { struct ComposeContainerView: View {
let mastodonController: MastodonController let mastodonController: MastodonController
let vcWidthSubject: PassthroughSubject<CGFloat, Never>
@ObservedObject var uiState: ComposeUIState @ObservedObject var uiState: ComposeUIState
init( init(
mastodonController: MastodonController, mastodonController: MastodonController,
vcWidthSubject: PassthroughSubject<CGFloat, Never>,
uiState: ComposeUIState uiState: ComposeUIState
) { ) {
self.mastodonController = mastodonController self.mastodonController = mastodonController
self.vcWidthSubject = vcWidthSubject
self.uiState = uiState self.uiState = uiState
} }
var body: some View { var body: some View {
ComposeView( ComposeView(draft: uiState.draft)
draft: uiState.draft,
vcWidthSubject: vcWidthSubject
)
.environmentObject(mastodonController) .environmentObject(mastodonController)
.environmentObject(uiState) .environmentObject(uiState)
} }

View File

@ -16,9 +16,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
let mastodonController: MastodonController let mastodonController: MastodonController
let uiState: ComposeUIState let uiState: ComposeUIState
// storing the width in the UI state and having SwiftUI listen to it via @ObservedObject doesn't work
// it ends up spinning forever
let widthSubject = PassthroughSubject<CGFloat, Never>()
var draft: Draft { uiState.draft } var draft: Draft { uiState.draft }
@ -42,7 +39,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
// to use as the UIHostingController type parameter // to use as the UIHostingController type parameter
let container = ComposeContainerView( let container = ComposeContainerView(
mastodonController: mastodonController, mastodonController: mastodonController,
vcWidthSubject: widthSubject,
uiState: uiState uiState: uiState
) )
super.init(rootView: container) super.init(rootView: container)
@ -82,12 +78,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
widthSubject.send(view.bounds.width)
}
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)

View File

@ -12,37 +12,33 @@ struct ComposeReplyContentView: UIViewRepresentable {
typealias UIViewType = ComposeReplyContentTextView typealias UIViewType = ComposeReplyContentTextView
let status: StatusMO let status: StatusMO
let maxWidth: CGFloat
@EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var mastodonController: MastodonController
let heightChanged: (CGFloat) -> Void
func makeUIView(context: Context) -> ComposeReplyContentTextView { func makeUIView(context: Context) -> ComposeReplyContentTextView {
let view = ComposeReplyContentTextView() let view = ComposeReplyContentTextView()
view.overrideMastodonController = mastodonController view.overrideMastodonController = mastodonController
view.setTextFrom(status: status) view.setTextFrom(status: status)
view.isScrollEnabled = false
view.isUserInteractionEnabled = false view.isUserInteractionEnabled = false
view.backgroundColor = .clear view.backgroundColor = .clear
view.maxWidth = maxWidth
return view return view
} }
func updateUIView(_ uiView: ComposeReplyContentTextView, context: Context) { func updateUIView(_ uiView: ComposeReplyContentTextView, context: Context) {
uiView.constraint.constant = maxWidth uiView.heightChanged = heightChanged
} }
} }
class ComposeReplyContentTextView: StatusContentTextView { class ComposeReplyContentTextView: StatusContentTextView {
var heightChanged: ((CGFloat) -> Void)?
var maxWidth: CGFloat! override func layoutSubviews() {
var constraint: NSLayoutConstraint! super.layoutSubviews()
override func didMoveToSuperview() { heightChanged?(contentSize.height)
super.didMoveToSuperview()
translatesAutoresizingMaskIntoConstraints = false
constraint = widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth)
constraint.isActive = true
} }
} }

View File

@ -10,9 +10,10 @@ import SwiftUI
struct ComposeReplyView: View { struct ComposeReplyView: View {
let status: StatusMO let status: StatusMO
let maxWidth: CGFloat
let stackPadding: CGFloat let stackPadding: CGFloat
@State private var contentHeight: CGFloat?
private let horizSpacing: CGFloat = 8 private let horizSpacing: CGFloat = 8
var body: some View { var body: some View {
@ -35,7 +36,10 @@ struct ComposeReplyView: View {
Spacer() Spacer()
} }
ComposeReplyContentView(status: status, maxWidth: maxWidth - 50 - horizSpacing + 4) ComposeReplyContentView(status: status) { (newHeight) in
self.contentHeight = newHeight
}
.frame(height: contentHeight)
.offset(x: -4, y: -8) .offset(x: -4, y: -8)
.padding(.bottom, -8) .padding(.bottom, -8)
} }

View File

@ -12,10 +12,8 @@ import Combine
struct ComposeView: View { struct ComposeView: View {
@ObservedObject var draft: Draft @ObservedObject var draft: Draft
let vcWidthSubject: PassthroughSubject<CGFloat, Never>
@EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var mastodonController: MastodonController
@State var viewControllerWidth: CGFloat = 0
@EnvironmentObject var uiState: ComposeUIState @EnvironmentObject var uiState: ComposeUIState
@State var isPosting = false @State var isPosting = false
@State var postProgress: Double = 0 @State var postProgress: Double = 0
@ -25,12 +23,8 @@ struct ComposeView: View {
private let stackPadding: CGFloat = 8 private let stackPadding: CGFloat = 8
init( init(draft: Draft) {
draft: Draft,
vcWidthSubject: PassthroughSubject<CGFloat, Never>
) {
self.draft = draft self.draft = draft
self.vcWidthSubject = vcWidthSubject
} }
var charactersRemaining: Int { var charactersRemaining: Int {
@ -73,7 +67,6 @@ struct ComposeView: View {
.coordinateSpace(name: "outer") .coordinateSpace(name: "outer")
.onAppear(perform: self.didAppear) .onAppear(perform: self.didAppear)
.navigationBarTitle("Compose") .navigationBarTitle("Compose")
.onReceive(vcWidthSubject) { self.viewControllerWidth = $0 }
.actionSheet(isPresented: $uiState.isShowingSaveDraftSheet, content: self.saveAndCloseSheet) .actionSheet(isPresented: $uiState.isShowingSaveDraftSheet, content: self.saveAndCloseSheet)
.alert(isPresented: $isShowingPostErrorAlert) { .alert(isPresented: $isShowingPostErrorAlert) {
Alert( Alert(
@ -90,7 +83,6 @@ struct ComposeView: View {
let status = mastodonController.persistentContainer.status(for: id) { let status = mastodonController.persistentContainer.status(for: id) {
ComposeReplyView( ComposeReplyView(
status: status, status: status,
maxWidth: viewControllerWidth - (2 * stackPadding),
stackPadding: stackPadding stackPadding: stackPadding
) )
} }