Merge pull request #5781 from vector-im/andy/5780_indicator_context

Replace user indicator presenting view controller with context
This commit is contained in:
Anderas 2022-03-09 12:28:50 +00:00 committed by GitHub
commit 9b922a3bc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 116 additions and 40 deletions

View file

@ -0,0 +1,42 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import UIKit
/// The presentation context is used by `UserIndicatorViewPresentable`s to display content
/// on the screen and it serves two primary purposes:
///
/// - abstraction on top of UIKit (passing context instead of view controllers)
/// - immutable context passed at init with variable presenting view controller
/// (e.g. depending on collapsed / uncollapsed iPad presentation that changes
/// at runtime)
public protocol UserIndicatorPresentationContext {
var indicatorPresentingViewController: UIViewController? { get }
}
/// A simple implementation of `UserIndicatorPresentationContext` that uses a weak reference
/// to the passed-in view controller as the presentation context.
public class StaticUserIndicatorPresentationContext: UserIndicatorPresentationContext {
// The presenting view controller will be the parent of the user indicator,
// and the indicator holds a strong reference to the context, so the view controller
// must be decleared `weak` to avoid a retain cycle
public private(set) weak var indicatorPresentingViewController: UIViewController?
public init(viewController: UIViewController) {
self.indicatorPresentingViewController = viewController
}
}

View file

@ -47,26 +47,19 @@ protocol UserIndicatorTypePresenterProtocol {
}
class UserIndicatorTypePresenter: UserIndicatorTypePresenterProtocol {
private weak var viewController: UIViewController?
// In the existing app architecture it is often view controllers which instantiate
// various presenters (errors, alerts ... ) and present on self. Since the presenting view controller
// needs to be passed on init, it must be declared as weak, otherwise a retain cycle would occur.
private var presentingViewController: UIViewController {
guard let viewController = viewController else {
MXLog.error("[UserIndicatorTypePresenter]: Presenting view controller is not available")
return UIViewController()
}
return viewController
}
private let presentationContext: UserIndicatorPresentationContext
let queue: UserIndicatorQueue
init(presentingViewController: UIViewController) {
self.viewController = presentingViewController
init(presentationContext: UserIndicatorPresentationContext) {
self.presentationContext = presentationContext
self.queue = UserIndicatorQueue()
}
convenience init(presentingViewController: UIViewController) {
let context = StaticUserIndicatorPresentationContext(viewController: presentingViewController)
self.init(presentationContext: context)
}
func present(_ type: UserIndicatorType) -> UserIndicator {
let request = userIndicatorRequest(for: type)
return queue.add(request)
@ -91,7 +84,7 @@ class UserIndicatorTypePresenter: UserIndicatorTypePresenterProtocol {
style: .loading,
label: label
),
presentingViewController: presentingViewController
presentationContext: presentationContext
)
return UserIndicatorRequest(
presenter: presenter,
@ -102,7 +95,7 @@ class UserIndicatorTypePresenter: UserIndicatorTypePresenterProtocol {
private func fullScreenLoadingRequest(label: String) -> UserIndicatorRequest {
let presenter = FullscreenLoadingViewPresenter(
label: label,
presentingViewController: presentingViewController
presentationContext: presentationContext
)
return UserIndicatorRequest(
presenter: presenter,
@ -116,7 +109,7 @@ class UserIndicatorTypePresenter: UserIndicatorTypePresenterProtocol {
style: .success,
label: label
),
presentingViewController: presentingViewController
presentationContext: presentationContext
)
return UserIndicatorRequest(
presenter: presenter,

View file

@ -22,18 +22,18 @@ import UIKit
/// It is managed by a `UserIndicator`, meaning the `present` and `dismiss` methods will be called when the parent `UserIndicator` starts or completes.
class FullscreenLoadingViewPresenter: UserIndicatorViewPresentable {
private let label: String
private weak var viewController: UIViewController?
private let presentationContext: UserIndicatorPresentationContext
private weak var view: UIView?
private var animator: UIViewPropertyAnimator?
init(label: String, presentingViewController: UIViewController) {
init(label: String, presentationContext: UserIndicatorPresentationContext) {
self.label = label
self.viewController = presentingViewController
self.presentationContext = presentationContext
}
func present() {
// Find the current top navigation controller
var presentingController: UIViewController? = viewController
var presentingController: UIViewController? = presentationContext.indicatorPresentingViewController
while presentingController?.navigationController != nil {
presentingController = presentingController?.navigationController
}

View file

@ -27,17 +27,17 @@ class ToastViewPresenter: UserIndicatorViewPresentable {
}
private let viewState: ToastViewState
private weak var viewController: UIViewController?
private let presentationContext: UserIndicatorPresentationContext
private weak var view: UIView?
private var animator: UIViewPropertyAnimator?
init(viewState: ToastViewState, presentingViewController: UIViewController) {
init(viewState: ToastViewState, presentationContext: UserIndicatorPresentationContext) {
self.viewState = viewState
self.viewController = presentingViewController
self.presentationContext = presentationContext
}
func present() {
guard let viewController = viewController else {
guard let viewController = presentationContext.indicatorPresentingViewController else {
return
}

View file

@ -16,6 +16,7 @@
import Foundation
import MatrixSDK
import CommonKit
/// SplitViewCoordinatorParameters input parameters
class SplitViewCoordinatorParameters {
@ -104,7 +105,13 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
// Setup split view controller
self.splitViewController.viewControllers = [tabBarCoordinator.toPresentable(), detailNavigationController]
updateUserIndicatorPresenter()
// Setup detail user indicator presenter
let context = SplitViewUserIndicatorPresentationContext(
splitViewController: splitViewController,
tabBarCoordinator: tabBarCoordinator,
detailNavigationController: detailNavigationController
)
detailUserIndicatorPresenter = UserIndicatorTypePresenter(presentationContext: context)
self.add(childCoordinator: tabBarCoordinator)
@ -260,16 +267,6 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
}
}
}
private func updateUserIndicatorPresenter() {
guard let tabBarCoordinator = tabBarCoordinator, let detailNavigationController = detailNavigationController else {
MXLog.debug("[SplitViewCoordinator]: Missing tab bar or detail coordinator, cannot update user indicator presenter")
return
}
let presentingViewController = splitViewController.isCollapsed ? tabBarCoordinator.toPresentable() : detailNavigationController
detailUserIndicatorPresenter = UserIndicatorTypePresenter(presentingViewController: presentingViewController)
}
}
// MARK: - UISplitViewControllerDelegate
@ -299,8 +296,6 @@ extension SplitViewCoordinator: UISplitViewControllerDelegate {
// Restore detail navigation controller with placeholder as root
self.resetDetailNavigationController(animated: false)
updateUserIndicatorPresenter()
// Return up to date detail navigation controller
// In any cases `detailNavigationController` will be used as secondary view of the split view controller.
return self.detailNavigationController
@ -311,7 +306,6 @@ extension SplitViewCoordinator: UISplitViewControllerDelegate {
/// or true to indicate that you do not want the split view controller to do anything with the secondary view controller.
/// Sample case: large iPhone goes from landscape to portrait.
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
updateUserIndicatorPresenter()
// If the secondary view is the placeholder screen do not merge the secondary into the primary.
// Note: In this case, the secondaryViewController will be automatically discarded.

View file

@ -0,0 +1,46 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import CommonKit
class SplitViewUserIndicatorPresentationContext: UserIndicatorPresentationContext {
private weak var splitViewController: UISplitViewController?
private weak var tabBarCoordinator: TabBarCoordinator?
private weak var detailNavigationController: UINavigationController?
init(
splitViewController: UISplitViewController,
tabBarCoordinator: TabBarCoordinator,
detailNavigationController: UINavigationController
) {
self.splitViewController = splitViewController
self.tabBarCoordinator = tabBarCoordinator
self.detailNavigationController = detailNavigationController
}
var indicatorPresentingViewController: UIViewController? {
guard
let splitViewController = splitViewController,
let tabBarCoordinator = tabBarCoordinator,
let detailNavigationController = detailNavigationController
else {
MXLog.debug("[SplitViewCoordinator]: Missing tab bar or detail coordinator, cannot update user indicator presenter")
return nil
}
return splitViewController.isCollapsed ? tabBarCoordinator.toPresentable() : detailNavigationController
}
}

1
changelog.d/5780.change Normal file
View file

@ -0,0 +1 @@
Activity Indicators: Replace user indicator presenting view controller with context