diff --git a/Riot/Categories/UITableViewCell.swift b/Riot/Categories/UITableViewCell.swift index 86c4b7ee0..6071a11ec 100644 --- a/Riot/Categories/UITableViewCell.swift +++ b/Riot/Categories/UITableViewCell.swift @@ -51,5 +51,16 @@ extension UITableViewCell { @objc func vc_setAccessoryDisclosureIndicatorWithCurrentTheme() { self.vc_setAccessoryDisclosureIndicator(withTheme: ThemeService.shared().theme) } + + @objc var vc_parentViewController: UIViewController? { + var parent: UIResponder? = self + while parent != nil { + parent = parent?.next + if let viewController = parent as? UIViewController { + return viewController + } + } + return nil + } } diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift index 141c676e2..ef1c2a66d 100644 --- a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift +++ b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift @@ -25,8 +25,7 @@ import Combine class VectorHostingController: UIHostingController { // MARK: Private - - private let forceZeroSafeAreaInsets: Bool + private var theme: Theme private var heightSubject = CurrentValueSubject(0) @@ -55,11 +54,8 @@ class VectorHostingController: UIHostingController { } /// Initializer /// - Parameter rootView: Root view for the controller. - /// - Parameter forceZeroSafeAreaInsets: Whether to force-set the hosting view's safe area insets to zero. Useful when the view is used as part of a table view. - init(rootView: Content, - forceZeroSafeAreaInsets: Bool = false) where Content: View { + init(rootView: Content) where Content: View { self.theme = ThemeService.shared().theme - self.forceZeroSafeAreaInsets = forceZeroSafeAreaInsets super.init(rootView: AnyView(rootView.vectorContent())) } @@ -116,22 +112,6 @@ class VectorHostingController: UIHostingController { heightSubject.send(height) } } - - override func viewSafeAreaInsetsDidChange() { - super.viewSafeAreaInsetsDidChange() - - guard forceZeroSafeAreaInsets else { - return - } - - let counterSafeAreaInsets = UIEdgeInsets(top: -view.safeAreaInsets.top, - left: -view.safeAreaInsets.left, - bottom: -view.safeAreaInsets.bottom, - right: -view.safeAreaInsets.right) - if additionalSafeAreaInsets != counterSafeAreaInsets, counterSafeAreaInsets != .zero { - additionalSafeAreaInsets = counterSafeAreaInsets - } - } private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) diff --git a/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift b/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift index 5aa5f10e5..f33762144 100644 --- a/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift +++ b/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift @@ -16,6 +16,7 @@ import UIKit import MatrixSDK +import SwiftUI @objc protocol SizableBaseRoomCellType: BaseRoomCellProtocol { static func sizingViewHeightHashValue(from bubbleCellData: MXKRoomBubbleCellData) -> Int @@ -35,6 +36,7 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { private static let reactionsViewModelBuilder = RoomReactionsViewModelBuilder() private static let urlPreviewViewSizer = URLPreviewViewSizer() + private var contentVC: UIViewController? private class var sizingView: SizableBaseRoomCell { let sizingView: SizableBaseRoomCell @@ -115,6 +117,10 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { sizingView.setNeedsLayout() sizingView.layoutIfNeeded() + if let contentVC = sizingView.contentVC as? UIHostingController { + contentVC.view.invalidateIntrinsicContentSize() + } + let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height) var height = sizingView.systemLayoutSizeFitting(fittingSize).height @@ -168,4 +174,24 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { return height } + + func addContentViewController(_ controller: UIViewController, on contentView: UIView) { + controller.view.invalidateIntrinsicContentSize() + + let parent = vc_parentViewController + parent?.addChild(controller) + contentView.vc_addSubViewMatchingParent(controller.view) + controller.didMove(toParent: parent) + + contentVC = controller + } + + override func prepareForReuse() { + contentVC?.removeFromParent() + contentVC?.view.removeFromSuperview() + contentVC?.didMove(toParent: nil) + contentVC = nil + + super.prepareForReuse() + } } diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift index b69abdcd4..993b606c5 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift @@ -36,10 +36,10 @@ class PollBaseBubbleCell: PollPlainCell { self.setupBubbleBackgroundView() } - override func addPollView(_ pollView: UIView, on contentView: UIView) { - super.addPollView(pollView, on: contentView) + override func addContentViewController(_ controller: UIViewController, on contentView: UIView) { + super.addContentViewController(controller, on: contentView) - self.addBubbleBackgroundViewIfNeeded(for: pollView) + self.addBubbleBackgroundViewIfNeeded(for: controller.view) } // MARK: - Private diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift index a05f00285..67db62e88 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift @@ -36,10 +36,10 @@ class VoiceBroadcastBubbleCell: VoiceBroadcastPlainCell { self.setupBubbleBackgroundView() } - override func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) { - super.addVoiceBroadcastView(voiceBroadcastView, on: contentView) - - self.addBubbleBackgroundViewIfNeeded(for: voiceBroadcastView) + override func addContentViewController(_ controller: UIViewController, on contentView: UIView) { + super.addContentViewController(controller, on: contentView) + + self.addBubbleBackgroundViewIfNeeded(for: controller.view) } // MARK: - Private diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift index 345f0de95..70cf4370f 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift @@ -17,8 +17,7 @@ import Foundation class PollPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable { - - private var pollView: UIView? + private var event: MXEvent? override func render(_ cellData: MXKCellData!) { @@ -28,12 +27,12 @@ class PollPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCell let bubbleData = cellData as? RoomBubbleCellData, let event = bubbleData.events.last, event.eventType == __MXEventType.pollStart, - let view = TimelinePollProvider.shared.buildTimelinePollViewForEvent(event) else { + let controller = TimelinePollProvider.shared.buildTimelinePollVCForEvent(event) else { return } self.event = event - self.addPollView(view, on: contentView) + self.addContentViewController(controller, on: contentView) } override func setupViews() { @@ -52,13 +51,6 @@ class PollPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCell delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event]) } - - func addPollView(_ pollView: UIView, on contentView: UIView) { - - self.pollView?.removeFromSuperview() - contentView.vc_addSubViewMatchingParent(pollView) - self.pollView = pollView - } } extension PollPlainCell: RoomCellThreadSummaryDisplayable {} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift index 967f4cef8..989db3876 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift @@ -18,7 +18,6 @@ import Foundation class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable { - private var voiceBroadcastView: UIView? private var event: MXEvent? override func render(_ cellData: MXKCellData!) { @@ -29,12 +28,12 @@ class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable let event = bubbleData.events.last, let voiceBroadcastContent = VoiceBroadcastInfo(fromJSON: event.content), voiceBroadcastContent.state == VoiceBroadcastInfo.State.started.rawValue, - let view = TimelineVoiceBroadcastProvider.shared.buildTimelineVoiceBroadcastViewForEvent(event) else { + let controller = TimelineVoiceBroadcastProvider.shared.buildTimelineVoiceBroadcastVCForEvent(event) else { return } self.event = event - self.addVoiceBroadcastView(view, on: contentView) + self.addContentViewController(controller, on: contentView) } override func setupViews() { @@ -53,13 +52,6 @@ class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event]) } - - func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) { - - self.voiceBroadcastView?.removeFromSuperview() - contentView.vc_addSubViewMatchingParent(voiceBroadcastView) - self.voiceBroadcastView = voiceBroadcastView - } } extension VoiceBroadcastPlainCell: RoomCellThreadSummaryDisplayable {} diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift index a587b23d8..1acd907a4 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift @@ -84,8 +84,7 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel func start() { } func toPresentable() -> UIViewController { - VectorHostingController(rootView: TimelinePollView(viewModel: viewModel.context), - forceZeroSafeAreaInsets: true) + VectorHostingController(rootView: TimelinePollView(viewModel: viewModel.context)) } func canEndPoll() -> Bool { diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift index 78b1d8ab7..31fb63849 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift @@ -26,13 +26,13 @@ class TimelinePollProvider { /// Create or retrieve the poll timeline coordinator for this event and return /// a view to be displayed in the timeline - func buildTimelinePollViewForEvent(_ event: MXEvent) -> UIView? { + func buildTimelinePollVCForEvent(_ event: MXEvent) -> UIViewController? { guard let session = session, let room = session.room(withRoomId: event.roomId) else { return nil } if let coordinator = coordinatorsForEventIdentifiers[event.eventId] { - return coordinator.toPresentable().view + return coordinator.toPresentable() } let parameters = TimelinePollCoordinatorParameters(session: session, room: room, pollStartEvent: event) @@ -42,7 +42,7 @@ class TimelinePollProvider { coordinatorsForEventIdentifiers[event.eventId] = coordinator - return coordinator.toPresentable().view + return coordinator.toPresentable() } /// Retrieve the poll timeline coordinator for the given event or nil if it hasn't been created yet diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift index 65c618860..b8181ae02 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift @@ -61,8 +61,7 @@ final class TimelineVoiceBroadcastCoordinator: Coordinator, Presentable, VoiceBr func start() { } func toPresentable() -> UIViewController { - VectorHostingController(rootView: TimelineVoiceBroadcastView(viewModel: viewModel.context), - forceZeroSafeAreaInsets: true) + VectorHostingController(rootView: TimelineVoiceBroadcastView(viewModel: viewModel.context)) } func canEndVoiceBroadcast() -> Bool { diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastProvider.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastProvider.swift index 327da466d..c5ae2b330 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastProvider.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastProvider.swift @@ -26,13 +26,13 @@ class TimelineVoiceBroadcastProvider { /// Create or retrieve the voiceBroadcast timeline coordinator for this event and return /// a view to be displayed in the timeline - func buildTimelineVoiceBroadcastViewForEvent(_ event: MXEvent) -> UIView? { + func buildTimelineVoiceBroadcastVCForEvent(_ event: MXEvent) -> UIViewController? { guard let session = session, let room = session.room(withRoomId: event.roomId) else { return nil } if let coordinator = coordinatorsForEventIdentifiers[event.eventId] { - return coordinator.toPresentable().view + return coordinator.toPresentable() } let parameters = TimelineVoiceBroadcastCoordinatorParameters(session: session, room: room, voiceBroadcastStartEvent: event) @@ -42,7 +42,7 @@ class TimelineVoiceBroadcastProvider { coordinatorsForEventIdentifiers[event.eventId] = coordinator - return coordinator.toPresentable().view + return coordinator.toPresentable() } /// Retrieve the voiceBroadcast timeline coordinator for the given event or nil if it hasn't been created yet diff --git a/changelog.d/5326.bugfix b/changelog.d/5326.bugfix new file mode 100644 index 000000000..8eaa35254 --- /dev/null +++ b/changelog.d/5326.bugfix @@ -0,0 +1 @@ +Timeline: Fix layout for SwiftUI content views.