Skip to content

Commit 60fbdb6

Browse files
committed
task: Code review changes
1 parent 32e9fd3 commit 60fbdb6

File tree

1 file changed

+175
-15
lines changed

1 file changed

+175
-15
lines changed

Sources/GoodSwiftUI/ReadableContentWidthModifier.swift

Lines changed: 175 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,193 @@
77

88
import SwiftUI
99

10-
private struct ReadableContentWidth: ViewModifier {
10+
// MARK: - Readable content width view
1111

12-
private let measureViewController = UIViewController()
12+
public struct FittingReadableWidth<Content: View>: View {
1313

14-
@State private var orientation: UIDeviceOrientation = UIDevice.current.orientation
14+
private let alignment: Alignment
15+
private let content: () -> Content
16+
17+
public init(alignment: Alignment = .center, content: @escaping () -> Content) {
18+
self.alignment = alignment
19+
self.content = content
20+
}
21+
22+
public var body: some View {
23+
content().fittingReadableWidth(alignment: alignment)
24+
}
25+
26+
}
27+
28+
// MARK: - Readable content width modifier
29+
30+
private struct ReadableContentWidthModifier: ViewModifier {
31+
32+
let alignment: Alignment
1533

1634
func body(content: Content) -> some View {
1735
content
18-
.frame(maxWidth: readableWidth(for: orientation))
19-
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
20-
orientation = UIDevice.current.orientation
21-
}
36+
.modifier(ReadableContentWidthPaddingModifier(alignment: alignment))
37+
.modifier(ReadableContentWidthMeasurementModifier())
2238
}
2339

24-
private func readableWidth(for _: UIDeviceOrientation) -> CGFloat {
25-
measureViewController.view.frame = UIScreen.main.bounds
26-
let readableContentSize = measureViewController.view.readableContentGuide.layoutFrame.size
27-
return readableContentSize.width
40+
}
41+
42+
public extension View {
43+
44+
func fittingReadableWidth(alignment: Alignment = .center) -> some View {
45+
modifier(ReadableContentWidthModifier(alignment: alignment))
2846
}
2947

3048
}
3149

32-
public extension View {
50+
// MARK: - Environment
51+
52+
private extension EnvironmentValues {
53+
54+
@Entry var readableContentInsets = EdgeInsets()
55+
56+
}
57+
58+
// MARK: - Padding modifier
59+
60+
private struct ReadableContentWidthPaddingModifier: ViewModifier {
3361

34-
func readableContentWidth(_ edges: Edge.Set = .horizontal, _ length: CGFloat = 16) -> some View {
35-
let modifiedView = modifier(ReadableContentWidth())
36-
return modifiedView.padding(edges, length)
62+
@Environment(\.readableContentInsets) private var readableContentInsets
63+
64+
let alignment: Alignment
65+
66+
func body(content: Content) -> some View {
67+
content
68+
.frame(maxWidth: .infinity, alignment: alignment)
69+
.padding(.leading, readableContentInsets.leading)
70+
.padding(.trailing, readableContentInsets.trailing)
3771
}
3872

3973
}
74+
75+
// MARK: - Measurement modifier
76+
77+
private struct ReadableContentWidthMeasurementModifier: ViewModifier {
78+
79+
@State private var readableContentInsets = EdgeInsets()
80+
81+
func body(content: Content) -> some View {
82+
content
83+
.environment(\.readableContentInsets, readableContentInsets)
84+
.background(LayoutGuides(onChangeOfReadableContentInsets: {
85+
readableContentInsets = $0
86+
}))
87+
}
88+
89+
}
90+
91+
// MARK: - UIKit LayoutGuides view
92+
93+
private struct LayoutGuides: UIViewRepresentable {
94+
95+
let onChangeOfReadableContentInsets: (EdgeInsets) -> ()
96+
97+
func makeUIView(context: Context) -> LayoutGuidesView {
98+
let uiView = LayoutGuidesView()
99+
uiView.onChangeOfReadableContentInsets = self.onChangeOfReadableContentInsets
100+
return uiView
101+
}
102+
103+
func updateUIView(_ uiView: LayoutGuidesView, context: Context) {
104+
uiView.onChangeOfReadableContentInsets = self.onChangeOfReadableContentInsets
105+
}
106+
107+
}
108+
109+
private final class LayoutGuidesView: UIView {
110+
111+
fileprivate var onChangeOfReadableContentInsets: (EdgeInsets) -> () = { _ in }
112+
private var previousReadableContentInsets: EdgeInsets?
113+
114+
override func layoutMarginsDidChange() {
115+
super.layoutMarginsDidChange()
116+
updateReadableContent()
117+
}
118+
119+
override func layoutSubviews() {
120+
super.layoutSubviews()
121+
updateReadableContent()
122+
}
123+
124+
override var frame: CGRect {
125+
didSet { updateReadableContent() }
126+
}
127+
128+
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
129+
super.traitCollectionDidChange(previousTraitCollection)
130+
131+
if traitCollection.layoutDirection != previousTraitCollection?.layoutDirection {
132+
updateReadableContent()
133+
}
134+
}
135+
136+
}
137+
138+
private extension LayoutGuidesView {
139+
140+
func updateReadableContent() {
141+
let isRTLLanguage = traitCollection.layoutDirection == .rightToLeft
142+
let readableLayoutFrame = readableContentGuide.layoutFrame
143+
144+
let readableEdgeInsets = UIEdgeInsets(
145+
top: readableLayoutFrame.minY - bounds.minY,
146+
left: readableLayoutFrame.minX - bounds.minX,
147+
bottom: -(readableLayoutFrame.maxY - bounds.maxY),
148+
right: -(readableLayoutFrame.maxX - bounds.maxX)
149+
)
150+
let readableContentInsets = EdgeInsets(
151+
top: readableEdgeInsets.top,
152+
leading: isRTLLanguage ? readableEdgeInsets.right : readableEdgeInsets.left,
153+
bottom: readableEdgeInsets.bottom,
154+
trailing: isRTLLanguage ? readableEdgeInsets.left : readableEdgeInsets.right
155+
)
156+
157+
guard previousReadableContentInsets != readableContentInsets else { return }
158+
defer { previousReadableContentInsets = readableContentInsets }
159+
160+
self.onChangeOfReadableContentInsets(readableContentInsets)
161+
}
162+
163+
}
164+
165+
// MARK: - Previews
166+
167+
@available(iOS 17.0, *)
168+
#Preview {
169+
FittingReadableWidth {
170+
Rectangle()
171+
.foregroundStyle(.cyan)
172+
}
173+
}
174+
175+
private final class PreviewViewController: UIViewController {
176+
177+
override func viewDidLoad() {
178+
super.viewDidLoad()
179+
180+
let rectangle = UIView()
181+
rectangle.translatesAutoresizingMaskIntoConstraints = false
182+
rectangle.backgroundColor = .systemRed
183+
184+
view.addSubview(rectangle)
185+
186+
NSLayoutConstraint.activate([
187+
rectangle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
188+
rectangle.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
189+
rectangle.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
190+
rectangle.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
191+
])
192+
}
193+
194+
}
195+
196+
@available(iOS 17.0, *)
197+
#Preview {
198+
PreviewViewController()
199+
}

0 commit comments

Comments
 (0)