-
Notifications
You must be signed in to change notification settings - Fork 134
Expand file tree
/
Copy pathPopover+Calculations.swift
More file actions
145 lines (126 loc) · 5.32 KB
/
Copy pathPopover+Calculations.swift
File metadata and controls
145 lines (126 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//
// Popover+Calculations.swift
//
//
// Created by A. Zheng (github.com/aheze) on 3/19/23.
// Copyright © 2023 A. Zheng. All rights reserved.
//
#if os(iOS)
import SwiftUI
public extension Popover {
/// Updates the popover's frame using its size.
func updateFrame(with size: CGSize?) {
let frame = calculateFrame(from: size)
context.size = size
context.staticFrame = frame
context.frame = frame
}
/// Calculate the popover's frame based on its size and position.
func calculateFrame(from size: CGSize?) -> CGRect {
guard let window = context.presentedPopoverContainer?.window else { return .zero }
switch attributes.position {
case let .absolute(originAnchor, popoverAnchor):
var popoverFrame = attributes.position.absoluteFrame(
originAnchor: originAnchor,
popoverAnchor: popoverAnchor,
originFrame: attributes.sourceFrame().inset(by: attributes.sourceFrameInset),
popoverSize: size ?? .zero,
isRightToLeft: context.isRightToLeft
)
let screenEdgePadding = attributes.screenEdgePadding
let leadingPadding = context.isRightToLeft ? screenEdgePadding.right : screenEdgePadding.left
let trailingPadding = context.isRightToLeft ? screenEdgePadding.left : screenEdgePadding.right
let safeWindowFrame = window.safeAreaLayoutGuide.layoutFrame
let minX = safeWindowFrame.minX + leadingPadding
let maxX = safeWindowFrame.maxX - trailingPadding
let minY = safeWindowFrame.minY + screenEdgePadding.top
let maxY = safeWindowFrame.maxY - screenEdgePadding.bottom
/// Popover overflows on left/top side.
if popoverFrame.origin.x < minX {
popoverFrame.origin.x = minX
}
if popoverFrame.origin.y < minY {
popoverFrame.origin.y = minY
}
/// Popover overflows on the right/bottom side.
if popoverFrame.maxX > maxX {
let difference = popoverFrame.maxX - maxX
popoverFrame.origin.x -= difference
}
if popoverFrame.maxY > maxY {
let difference = popoverFrame.maxY - maxY
popoverFrame.origin.y -= difference
}
return popoverFrame
case let .relative(popoverAnchors):
/// Set the selected anchor to the first one.
if context.selectedAnchor == nil {
context.selectedAnchor = popoverAnchors.first
}
let popoverFrame = attributes.position.relativeFrame(
selectedAnchor: context.selectedAnchor ?? popoverAnchors.first ?? .bottom,
containerFrame: attributes.sourceFrame().inset(by: attributes.sourceFrameInset),
popoverSize: size ?? .zero,
isRightToLeft: context.isRightToLeft
)
return popoverFrame
}
}
/// Calculate if the popover should be dismissed via drag **or** animated to another position (if using `.relative` positioning with multiple anchors). Called when the user stops dragging the popover.
func positionChanged(to point: CGPoint) {
let windowBounds = context.windowBounds
if
attributes.dismissal.mode.contains(.dragDown),
point.y >= windowBounds.height - windowBounds.height * attributes.dismissal.dragDismissalProximity
{
if attributes.dismissal.dragMovesPopoverOffScreen {
var newFrame = context.staticFrame
newFrame.origin.y = windowBounds.height
context.staticFrame = newFrame
context.frame = newFrame
}
dismiss()
return
}
if
attributes.dismissal.mode.contains(.dragUp),
point.y <= windowBounds.height * attributes.dismissal.dragDismissalProximity
{
if attributes.dismissal.dragMovesPopoverOffScreen {
var newFrame = context.staticFrame
newFrame.origin.y = -newFrame.height
context.staticFrame = newFrame
context.frame = newFrame
}
dismiss()
return
}
if
attributes.changeLocationOnDismiss
{
context.staticFrame = context.frame
return
}
if case let .relative(popoverAnchors) = attributes.position {
let frame = attributes.sourceFrame().inset(by: attributes.sourceFrameInset)
let size = context.size ?? .zero
let closestAnchor = attributes.position.relativeClosestAnchor(
popoverAnchors: popoverAnchors,
containerFrame: frame,
popoverSize: size,
targetPoint: point,
isRightToLeft: context.isRightToLeft
)
let popoverFrame = attributes.position.relativeFrame(
selectedAnchor: closestAnchor,
containerFrame: frame,
popoverSize: size,
isRightToLeft: context.isRightToLeft
)
context.selectedAnchor = closestAnchor
context.staticFrame = popoverFrame
context.frame = popoverFrame
}
}
}
#endif