Skip to content

Commit

Permalink
Add displayOrder in configuration of Container
Browse files Browse the repository at this point in the history
  • Loading branch information
fatbobman committed Apr 8, 2022
1 parent 980694c commit d18184b
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 21 deletions.
20 changes: 19 additions & 1 deletion Demo/Shared/Demos/DisplayTypeDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct DisplayTypeDemo: View {
@State var spacing: Double = 10
@State var containerConfiguration = DisplayTypeContainerConfiguration()
@State var viewConfiguration = DisplayTypeViewConfiguration()
@State var displayOrder: ContainerDisplayOrder = .ascending
@Environment(\.overlayContainerManager) var manager
var body: some View {
ZStack(alignment: .top) {
Expand Down Expand Up @@ -49,6 +50,22 @@ struct DisplayTypeDemo: View {
}
})

Picker("DisplayOrder", selection: $displayOrder) {
ForEach(ContainerDisplayOrder.allCases, id: \.rawValue) { displayOrder in
Text(LocalizedStringKey(displayOrder.rawValue))
.tag(displayOrder)
}
}
.pickerStyle(.segmented)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.onChange(of: displayOrder, perform: { _ in
reset()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
containerConfiguration.displayOrder = displayOrder
}
})

Text(displayType.information.description)
.multilineTextAlignment(.leading)
.frame(maxWidth: 500)
Expand Down Expand Up @@ -114,7 +131,7 @@ extension DisplayTypeDemo {
func sliderOfSpacing() -> some View {
VStack {
Text("SliderOfSpace \(String(format: "%.1f", spacing))")
Slider(value: $spacing, in: -20.0...30)
Slider(value: $spacing, in: -100.0...30)
.onChange(of: spacing, perform: { value in
containerConfiguration.spacing = value
})
Expand Down Expand Up @@ -180,6 +197,7 @@ struct DisplayTypeContainerConfiguration: ContainerConfigurationProtocol {
var alignment: Alignment? = .center
var spacing: CGFloat = 10
var insets: EdgeInsets = .init()
var displayOrder: ContainerDisplayOrder = .ascending

var shadowStyle: ContainerViewShadowStyle? {
.radius(10)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,14 @@ extension OverlayContainer {
containerViewDisplayType: containerConfiguration.displayType
)
// the current context of view is ZStack (GenericStack)
backgroundOfIdentifiableView
.zIndex(1.0)
compositingView
.padding(containerConfiguration.insets) // add insets for each view
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: alignment)
.zIndex(2.0)
ZStack {
backgroundOfIdentifiableView
.zIndex(1.0)
compositingView
.padding(containerConfiguration.insets) // add insets for each view
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: alignment)
.zIndex(2.0)
}
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions Sources/SwiftUIOverlayContainer/Container/Container.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ struct OverlayContainer: View {
queueType: configuration.queueType,
animation: configuration.animation,
delayForShowingNext: configuration.delayForShowingNext,
maximumNumberOfViewsInMultiple: configuration.maximumNumberOfViewsInMultipleMode
maximumNumberOfViewsInMultiple: configuration.maximumNumberOfViewsInMultipleMode,
displayOrder: configuration.displayOrder
)
_queueHandler = StateObject(wrappedValue: handler)
}
Expand Down Expand Up @@ -125,6 +126,7 @@ struct OverlayContainer: View {
)
.onAppear(perform: appearAction)
.onDisappear(perform: disappearAction)
.zIndex(timeStamp: identifiableView.timeStamp, order: configuration.displayOrder)
}
}
case .vertical, .horizontal:
Expand Down Expand Up @@ -163,6 +165,7 @@ struct OverlayContainer: View {
)
.onAppear(perform: appearAction)
.onDisappear(perform: disappearAction)
.zIndex(timeStamp: identifiableView.timeStamp, order: configuration.displayOrder)
}
}
.padding(insets)
Expand All @@ -179,10 +182,12 @@ struct OverlayContainer: View {
.onChange(of:
configuration.animation,
configuration.delayForShowingNext,
configuration.maximumNumberOfViewsInMultipleMode) { newAnimation, newDelay, newMaximumNumberOfViewsInMultipleMode in
configuration.maximumNumberOfViewsInMultipleMode,
configuration.displayOrder) { newAnimation, newDelay, newMaximumNumberOfViewsInMultipleMode, displayOrder in
queueHandler.animation = newAnimation
queueHandler.delayForShowingNext = newDelay
queueHandler.maximumNumberOfViewsInMultiple = newMaximumNumberOfViewsInMultipleMode
queueHandler.displayOrder = displayOrder
}
// Prohibition of changing the containerName and the queueType
.onChange(of: configuration.queueType, containerName, configuration.clipped) { _ in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public protocol ContainerCompositionProtocol {
///
/// Default value is disable
var ignoresSafeArea: ContainerIgnoresSafeArea { get }

/// Controls the display order of overlapping views.
///
/// Default value is ascending order, new container view can overlap the old one
var displayOrder: ContainerDisplayOrder { get }
}

public extension ContainerCompositionProtocol {
Expand All @@ -77,6 +82,8 @@ public extension ContainerCompositionProtocol {
var clipped: Bool { false }

var ignoresSafeArea: ContainerIgnoresSafeArea { .disable }

var displayOrder: ContainerDisplayOrder { .ascending }
}

/// A combined protocol that defines all the configuration of the container
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftUIOverlayContainer/Container/ContainerType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ public enum ContainerIgnoresSafeArea {
/// set custom regions and edges of safe area
case custom(SafeAreaRegions, Edge.Set)
}

/// Controls the display order of overlapping views.
public enum ContainerDisplayOrder: String, Hashable, Equatable, CaseIterable {
case ascending
case descending
}
21 changes: 18 additions & 3 deletions Sources/SwiftUIOverlayContainer/Container/QueueHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ final class ContainerQueueHandler: ObservableObject {
/// In OneByOneWaitFinish mode,The view in temporary queue are delayed for a specific number of seconds when the currently displayed view is dismissed.
var delayForShowingNext: TimeInterval

/// The display order of container view
var displayOrder: ContainerDisplayOrder

var maximumNumberOfViewsInMultiple: UInt {
didSet {
// if user change the max number of view at runtime, get more views from temp queue
Expand All @@ -71,13 +74,15 @@ final class ContainerQueueHandler: ObservableObject {
queueType: ContainerViewQueueType,
animation: Animation?,
delayForShowingNext: TimeInterval,
maximumNumberOfViewsInMultiple: UInt = UInt.max) {
maximumNumberOfViewsInMultiple: UInt = UInt.max,
displayOrder: ContainerDisplayOrder) {
self.container = container
self.queueType = queueType
self.animation = animation
self.delayForShowingNext = delayForShowingNext
self.manager = containerManager
self.maximumNumberOfViewsInMultiple = maximumNumberOfViewsInMultiple
self.displayOrder = displayOrder
}

/// Register the container in the container manager. This method will be called when the container appear ( not in container view init ).
Expand Down Expand Up @@ -155,14 +160,24 @@ extension ContainerQueueHandler {
}
}

/// Dismiss the topmost view ( with the largest zIndex , not the top one of main queue )
func dismissTopmostView(animated flag: Bool) {
if let theTopView = mainQueue.last {
dismiss(id: theTopView.id, animated: flag)
let theTopViewID: UUID?
switch displayOrder {
case .ascending:
theTopViewID = mainQueue.sorted(by: { $0.timeStamp > $1.timeStamp }).first?.id
case .descending:
theTopViewID = mainQueue.sorted(by: { $0.timeStamp < $1.timeStamp }).first?.id
}
if let id = theTopViewID {
dismiss(id: id, animated: flag)
}
}

/// Push view into specific queue
func pushViewIntoQueue(_ identifiableView: IdentifiableContainerView, queue: QueueType, animated flag: Bool = true) {
var identifiableView = identifiableView
identifiableView.timeStamp = Date()
switch queue {
case .main:
var animation: Animation = .disable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public struct IdentifiableContainerView: Identifiable {
/// A bind value that controls the display of the current container view, set false to dismiss the current view
let isPresented: Binding<Bool>?

/// The timestamp of push to display queue, used as a reference for zIndex
var timeStamp = Date()

public init<Context: View>(
id: UUID,
view: Context,
Expand Down
23 changes: 22 additions & 1 deletion Sources/SwiftUIOverlayContainer/Extensions/SwiftUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,20 @@ extension View {
.onChange(of: value2, perform: { newValue2 in action((value1, newValue2, value3)) })
.onChange(of: value3, perform: { newValue3 in action((value1, value2, newValue3)) })
}
}

func onChange<C0, C1, C2, C3>(of value1: C0,
_ value2: C1,
_ value3: C2,
_ value4: C3,
perform action: @escaping (_ newValues: (C0, C1, C2, C3)) -> Void)
-> some View where C0: Equatable, C1: Equatable, C2: Equatable, C3: Equatable {
self
.onChange(of: value1, perform: { newValue1 in action((newValue1, value2, value3, value4)) })
.onChange(of: value2, perform: { newValue2 in action((value1, newValue2, value3, value4)) })
.onChange(of: value3, perform: { newValue3 in action((value1, value2, newValue3, value4)) })
.onChange(of: value4, perform: { newValue4 in action((value1, value2, value3, newValue4)) })
}
}

extension View {
/// condition clip
Expand All @@ -113,4 +125,13 @@ extension View {
ignoresSafeArea(regions, edges: edges)
}
}

/// set zIndex by timeStamp of indentifiableView
func zIndex(timeStamp: Date, order: ContainerDisplayOrder) -> some View {
var index = timeStamp.timeIntervalSince1970
if case .descending = order {
index = Date.distantFuture.timeIntervalSince1970 - index
}
return zIndex(index)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class QueueHandlerForMultipleUnitTests: XCTestCase {
containerManager: manager,
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: containerConfiguration.delayForShowingNext
delayForShowingNext: containerConfiguration.delayForShowingNext,
displayOrder: .ascending
)
}

Expand Down Expand Up @@ -73,7 +74,8 @@ class QueueHandlerForMultipleUnitTests: XCTestCase {
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: containerConfiguration.delayForShowingNext,
maximumNumberOfViewsInMultiple: 1
maximumNumberOfViewsInMultiple: 1,
displayOrder: .ascending
)

let view1 = IdentifiableContainerView(
Expand Down Expand Up @@ -125,7 +127,8 @@ class QueueHandlerForMultipleUnitTests: XCTestCase {
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: 0.1,
maximumNumberOfViewsInMultiple: 1
maximumNumberOfViewsInMultiple: 1,
displayOrder: .ascending
)

let view1 = IdentifiableContainerView(
Expand Down Expand Up @@ -171,7 +174,7 @@ class QueueHandlerForMultipleUnitTests: XCTestCase {
XCTAssertEqual(handler.mainQueue.count, 0)
}

func testDismissTopmostView() throws {
func testDismissTopmostViewInAscendingOrder() throws {
// given
let view1 = IdentifiableContainerView(
id: UUID(), view: MessageView(), viewConfiguration: MessageView(), isPresented: nil
Expand All @@ -194,6 +197,30 @@ class QueueHandlerForMultipleUnitTests: XCTestCase {
XCTAssertEqual(handler.mainQueue.first?.id, view1.id)
}

func testDismissTopmostViewInDescendingOrder() throws {
// given
handler.displayOrder = .descending
let view1 = IdentifiableContainerView(
id: UUID(), view: MessageView(), viewConfiguration: MessageView(), isPresented: nil
)
let view2 = IdentifiableContainerView(
id: UUID(), view: MessageView(), viewConfiguration: MessageView(), isPresented: nil
)

let perform = handler.getStrategyHandler(for: .multiple)

// when
perform(.show(view1, false))
perform(.show(view2, false))

// dismiss view
perform(.dismissTopmostView(false))

// then
XCTAssertEqual(handler.mainQueue.count, 1)
XCTAssertEqual(handler.mainQueue.first?.id, view2.id)
}

func testShowViewAfterConnect() async throws {
// given
let view1 = MessageView()
Expand Down Expand Up @@ -290,7 +317,8 @@ class QueueHandlerForMultipleUnitTests: XCTestCase {
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: 0.02,
maximumNumberOfViewsInMultiple: 1
maximumNumberOfViewsInMultiple: 1,
displayOrder: .ascending
)

let view1 = IdentifiableContainerView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class QueueHandlerForOneByOneTests: XCTestCase {
containerManager: manager,
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: containerConfiguration.delayForShowingNext
delayForShowingNext: containerConfiguration.delayForShowingNext,
displayOrder: .ascending
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class QueueHandlerForOneByeOneWaitFinishTests: XCTestCase {
containerManager: manager,
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: containerConfiguration.delayForShowingNext
delayForShowingNext: containerConfiguration.delayForShowingNext,
displayOrder: .ascending
)
}

Expand Down
3 changes: 2 additions & 1 deletion Tests/SwiftUIOverlayContainerTests/QueueHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class QueueHandlerTests: XCTestCase {
containerManager: manager,
queueType: containerConfiguration.queueType,
animation: containerConfiguration.animation,
delayForShowingNext: containerConfiguration.delayForShowingNext
delayForShowingNext: containerConfiguration.delayForShowingNext,
displayOrder: .ascending
)
}

Expand Down

0 comments on commit d18184b

Please sign in to comment.