Page MenuHomeMusing Studio

No OneTemporary

diff --git a/iOS/PostEditor/PostBodyTextView.swift b/iOS/PostEditor/PostBodyTextView.swift
index 2de4636..2eaef56 100644
--- a/iOS/PostEditor/PostBodyTextView.swift
+++ b/iOS/PostEditor/PostBodyTextView.swift
@@ -1,64 +1,89 @@
-// Based on https://stackoverflow.com/a/56508132/1234545
+// Based on https://stackoverflow.com/a/56508132/1234545 and https://stackoverflow.com/a/48360549/1234545
import SwiftUI
-struct PostBodyTextView: UIViewRepresentable {
+class PostBodyCoordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
+ @Binding var text: String
+ @Binding var isFirstResponder: Bool
+ var lineSpacingMultiplier: CGFloat
+ var didBecomeFirstResponder: Bool = false
+ var postBodyTextView: PostBodyTextView
- class Coordinator: NSObject, UITextViewDelegate {
- @Binding var text: String
- @Binding var isFirstResponder: Bool
- var didBecomeFirstResponder: Bool = false
+ weak var textView: UITextView?
- init(text: Binding<String>, isFirstResponder: Binding<Bool>) {
- _text = text
- _isFirstResponder = isFirstResponder
- }
+ init(
+ _ textView: PostBodyTextView,
+ text: Binding<String>,
+ isFirstResponder: Binding<Bool>,
+ lineSpacingMultiplier: CGFloat
+ ) {
+ self.postBodyTextView = textView
+ _text = text
+ _isFirstResponder = isFirstResponder
+ self.lineSpacingMultiplier = lineSpacingMultiplier
+ }
- func textViewDidChangeSelection(_ textView: UITextView) {
- DispatchQueue.main.async {
- self.text = textView.text ?? ""
- }
+ func textViewDidChange(_ textView: UITextView) {
+ DispatchQueue.main.async {
+ self.postBodyTextView.text = textView.text ?? ""
}
}
+ func layoutManager(
+ _ layoutManager: NSLayoutManager,
+ lineSpacingAfterGlyphAt glyphIndex: Int,
+ withProposedLineFragmentRect rect: CGRect
+ ) -> CGFloat {
+ return 17 * lineSpacingMultiplier
+ }
+}
+
+struct PostBodyTextView: UIViewRepresentable {
@Binding var text: String
@Binding var textStyle: UIFont
@Binding var isFirstResponder: Bool
var lineSpacing: CGFloat
func makeUIView(context: UIViewRepresentableContext<PostBodyTextView>) -> UITextView {
let textView = UITextView(frame: .zero)
+
+ textView.isEditable = true
+ textView.isUserInteractionEnabled = true
+ textView.isScrollEnabled = true
+ textView.alwaysBounceVertical = false
+
+ context.coordinator.textView = textView
textView.delegate = context.coordinator
+ textView.layoutManager.delegate = context.coordinator
+
let font = textStyle
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
textView.font = fontMetrics.scaledFont(for: font)
textView.backgroundColor = UIColor.clear
+
return textView
}
- func makeCoordinator() -> PostBodyTextView.Coordinator {
- return Coordinator(text: $text, isFirstResponder: $isFirstResponder)
+ func makeCoordinator() -> PostBodyCoordinator {
+ return Coordinator(
+ self,
+ text: $text,
+ isFirstResponder: $isFirstResponder,
+ lineSpacingMultiplier: lineSpacing
+ )
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<PostBodyTextView>) {
- let attributedString = NSMutableAttributedString(string: text)
- let paragraphStyle = NSMutableParagraphStyle()
- paragraphStyle.lineSpacing = lineSpacing
- attributedString.addAttribute(
- NSAttributedString.Key.paragraphStyle,
- value: paragraphStyle,
- range: NSMakeRange(0, attributedString.length) // swiftlint:disable:this legacy_constructor
- )
+ uiView.text = text
- uiView.attributedText = attributedString
let font = textStyle
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
uiView.font = fontMetrics.scaledFont(for: font)
// We don't want the text field to become first responder every time SwiftUI refreshes the view.
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder = true
}
}
}
diff --git a/iOS/PostEditor/PostTextEditingView.swift b/iOS/PostEditor/PostTextEditingView.swift
index 0f85e84..74b6857 100644
--- a/iOS/PostEditor/PostTextEditingView.swift
+++ b/iOS/PostEditor/PostTextEditingView.swift
@@ -1,103 +1,103 @@
import SwiftUI
struct PostTextEditingView: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@ObservedObject var post: WFAPost
@Binding var updatingTitleFromServer: Bool
@Binding var updatingBodyFromServer: Bool
@State private var appearance: PostAppearance = .serif
@State private var titleTextStyle: UIFont = UIFont(name: "Lora-Regular", size: 26)!
@State private var titleTextHeight: CGFloat = 50
@State private var titleIsFirstResponder: Bool = true
@State private var bodyTextStyle: UIFont = UIFont(name: "Lora-Regular", size: 17)!
@State private var bodyIsFirstResponder: Bool = false
private let bodyLineSpacingMultiplier: CGFloat = 0.5
init(
post: ObservedObject<WFAPost>,
updatingTitleFromServer: Binding<Bool>,
updatingBodyFromServer: Binding<Bool>
) {
self._post = post
self._updatingTitleFromServer = updatingTitleFromServer
self._updatingBodyFromServer = updatingBodyFromServer
UITextView.appearance().backgroundColor = .clear
}
var titleFieldHeight: CGFloat {
let minHeight: CGFloat = 50
if titleTextHeight < minHeight {
return minHeight
}
return titleTextHeight
}
var body: some View {
VStack {
ZStack(alignment: .topLeading) {
if post.title.count == 0 {
Text("Title (optional)")
.font(Font(titleTextStyle))
.foregroundColor(Color(UIColor.placeholderText))
.padding(.horizontal, 4)
.padding(.vertical, 8)
}
PostTitleTextView(
text: $post.title,
textStyle: $titleTextStyle,
height: $titleTextHeight,
isFirstResponder: $titleIsFirstResponder
)
.frame(height: titleFieldHeight)
.onChange(of: post.title) { _ in
if post.status == PostStatus.published.rawValue && !updatingTitleFromServer {
post.status = PostStatus.edited.rawValue
}
if updatingTitleFromServer {
updatingTitleFromServer = false
}
}
}
ZStack(alignment: .topLeading) {
if post.body.count == 0 {
Text("Write…")
.font(Font(bodyTextStyle))
.foregroundColor(Color(UIColor.placeholderText))
.padding(.horizontal, 4)
.padding(.vertical, 8)
}
PostBodyTextView(
text: $post.body,
textStyle: $bodyTextStyle,
isFirstResponder: $bodyIsFirstResponder,
- lineSpacing: 17 * (
- horizontalSizeClass == .compact ? bodyLineSpacingMultiplier / 2 : bodyLineSpacingMultiplier
- )
+ lineSpacing: horizontalSizeClass == .compact
+ ? bodyLineSpacingMultiplier / 2
+ : bodyLineSpacingMultiplier
)
.onChange(of: post.body) { _ in
if post.status == PostStatus.published.rawValue && !updatingBodyFromServer {
post.status = PostStatus.edited.rawValue
}
if updatingBodyFromServer {
updatingBodyFromServer = false
}
}
}
}
.onChange(of: titleIsFirstResponder, perform: { _ in
self.bodyIsFirstResponder.toggle()
})
.onAppear(perform: {
switch post.appearance {
case "sans":
self.appearance = .sans
case "wrap", "mono", "code":
self.appearance = .mono
default:
self.appearance = .serif
}
self.titleTextStyle = UIFont(name: appearance.rawValue, size: 26)!
self.bodyTextStyle = UIFont(name: appearance.rawValue, size: 17)!
})
}
}
diff --git a/iOS/PostEditor/PostTitleTextView.swift b/iOS/PostEditor/PostTitleTextView.swift
index 434722c..72af11c 100644
--- a/iOS/PostEditor/PostTitleTextView.swift
+++ b/iOS/PostEditor/PostTitleTextView.swift
@@ -1,95 +1,95 @@
// Based on https://lostmoa.com/blog/DynamicHeightForTextFieldInSwiftUI/
// and https://stackoverflow.com/a/56508132/1234545
import SwiftUI
-class Coordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
+class PostTitleCoordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
@Binding var text: String
@Binding var isFirstResponder: Bool
var didBecomeFirstResponder: Bool = false
var postTitleTextView: PostTitleTextView
weak var textView: UITextView?
init(_ textView: PostTitleTextView, text: Binding<String>, isFirstResponder: Binding<Bool>) {
self.postTitleTextView = textView
_text = text
_isFirstResponder = isFirstResponder
}
- func textViewDidChangeSelection(_ textView: UITextView) {
+ func textViewDidChange(_ textView: UITextView) {
DispatchQueue.main.async {
self.postTitleTextView.text = textView.text ?? ""
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
self.isFirstResponder.toggle()
return false
}
return true
}
func layoutManager(
_ layoutManager: NSLayoutManager,
didCompleteLayoutFor textContainer: NSTextContainer?,
atEnd layoutFinishedFlag: Bool
) {
DispatchQueue.main.async {
guard let view = self.textView else {
return
}
let size = view.sizeThatFits(view.bounds.size)
if self.postTitleTextView.height != size.height {
self.postTitleTextView.height = size.height
}
}
}
}
struct PostTitleTextView: UIViewRepresentable {
@Binding var text: String
@Binding var textStyle: UIFont
@Binding var height: CGFloat
@Binding var isFirstResponder: Bool
func makeUIView(context: UIViewRepresentableContext<PostTitleTextView>) -> UITextView {
let textView = UITextView()
textView.isEditable = true
textView.isUserInteractionEnabled = true
textView.isScrollEnabled = true
textView.alwaysBounceVertical = false
context.coordinator.textView = textView
textView.delegate = context.coordinator
textView.layoutManager.delegate = context.coordinator
let font = textStyle
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
textView.font = fontMetrics.scaledFont(for: font)
textView.backgroundColor = UIColor.clear
return textView
}
- func makeCoordinator() -> Coordinator {
+ func makeCoordinator() -> PostTitleCoordinator {
return Coordinator(self, text: $text, isFirstResponder: $isFirstResponder)
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<PostTitleTextView>) {
uiView.text = text
let font = textStyle
let fontMetrics = UIFontMetrics(forTextStyle: .largeTitle)
uiView.font = fontMetrics.scaledFont(for: font)
// We don't want the text field to become first responder every time SwiftUI refreshes the view.
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder = true
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 12, 7:18 AM (5 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3653326

Event Timeline