From ba48785cd4a9c1178f41e2e7bdfeaf245524e132 Mon Sep 17 00:00:00 2001 From: Alican Aycil Date: Fri, 5 Jul 2024 20:47:02 +0200 Subject: [PATCH 1/5] [TextView] Fix readonly mode when text is empty --- .../TextEditor/View/SwiftUI/TextEditorView.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift index aa7e5f7c4..6e0f5caad 100644 --- a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift +++ b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift @@ -21,10 +21,14 @@ public struct TextEditorView: View { private var titleKey: String @FocusState private var isFocused: Bool - private var isPlaceholderHidden: Bool { + private var isPlaceholderTextHidden: Bool { return !self.titleKey.isEmpty && self.text.isEmpty && !self.viewModel.isFocused } + private var isPlaceholderHidden: Bool { + return self.isPlaceholderTextHidden || self.viewModel.isReadOnly + } + public init( _ titleKey: String = "", text: Binding, @@ -97,7 +101,7 @@ public struct TextEditorView: View { trailing: self.viewModel.horizontalSpacing - self.defaultTexEditorHorizontalPadding ) ) - .opacity(self.isPlaceholderHidden || self.viewModel.isReadOnly ? 0 : 1) + .opacity(self.isPlaceholderHidden ? 0 : 1) .accessibilityHidden(true) } @@ -107,11 +111,11 @@ public struct TextEditorView: View { ScrollView { HStack(spacing: 0) { VStack(spacing: 0) { - Text(self.isPlaceholderHidden && !self.viewModel.isReadOnly ? self.titleKey : self.$text.wrappedValue) + Text(self.isPlaceholderTextHidden ? self.titleKey : self.$text.wrappedValue) .font(self.viewModel.font.font) - .foregroundStyle(self.viewModel.isReadOnly ? self.viewModel.textColor.color : self.viewModel.placeholderColor.color) + .foregroundStyle(self.isPlaceholderTextHidden ? self.viewModel.placeholderColor.color : self.viewModel.textColor.color) .frame(maxWidth: .infinity, alignment: .leading) - .opacity(self.isPlaceholderHidden || self.viewModel.isReadOnly ? 1 : 0) + .opacity(self.isPlaceholderHidden ? 1 : 0) .accessibilityHidden(true) Spacer(minLength: 0) } From 9e57126639fa5c09fd6ec09959b460e48eaea767 Mon Sep 17 00:00:00 2001 From: Alican Aycil Date: Mon, 8 Jul 2024 10:03:23 +0200 Subject: [PATCH 2/5] [TextView_1046] Placeholder visibility issue fixed --- .../Components/TextEditor/View/SwiftUI/TextEditorView.swift | 4 ++-- .../Components/TextEditor/View/UIKit/TextEditorUIView.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift index 6e0f5caad..7d7c89f3c 100644 --- a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift +++ b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift @@ -22,7 +22,7 @@ public struct TextEditorView: View { @FocusState private var isFocused: Bool private var isPlaceholderTextHidden: Bool { - return !self.titleKey.isEmpty && self.text.isEmpty && !self.viewModel.isFocused + return !self.titleKey.isEmpty && self.text.isEmpty } private var isPlaceholderHidden: Bool { @@ -101,7 +101,7 @@ public struct TextEditorView: View { trailing: self.viewModel.horizontalSpacing - self.defaultTexEditorHorizontalPadding ) ) - .opacity(self.isPlaceholderHidden ? 0 : 1) + .opacity(!self.isPlaceholderHidden || self.isFocused ? 1 : 0) .accessibilityHidden(true) } diff --git a/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift b/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift index 10b7495b1..62e331a6f 100644 --- a/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift +++ b/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift @@ -17,7 +17,7 @@ public final class TextEditorUIView: UITextView { @ScaledUIMetric private var defaultSystemVerticalPadding: CGFloat = 8 @ScaledUIMetric private var scaleFactor: CGFloat = 1.0 - private let viewModel: TextEditorViewModel + private var viewModel: TextEditorViewModel! private var cancellables = Set() private var placeHolderConstarints: [NSLayoutConstraint]? private var placeHolderLabelYAnchor: NSLayoutConstraint? @@ -45,7 +45,7 @@ public final class TextEditorUIView: UITextView { self._delegate = newValue } get { - return self._delegate + return super.delegate } } @@ -293,7 +293,7 @@ extension TextEditorUIView: UITextViewDelegate { } public func textViewDidBeginEditing(_ textView: UITextView) { - self.hidePlaceHolder(true) + self.hidePlaceHolder(!textView.text.isEmpty) self._delegate?.textViewDidBeginEditing?(textView) } From d4140d0ed0b794efd930dc7711db3fa44932bce5 Mon Sep 17 00:00:00 2001 From: Alican Aycil Date: Mon, 15 Jul 2024 16:02:27 +0200 Subject: [PATCH 3/5] [TextView_1048] Fix focus mode --- .../UseCase/TextEditorBordersUseCase.swift | 8 +--- .../UseCase/TextEditorColorsUseCase.swift | 2 +- .../View/SwiftUI/TextEditorView.swift | 42 +++++++++++++++---- .../View/UIKit/TextEditorUIView.swift | 3 ++ 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/core/Sources/Components/TextEditor/UseCase/TextEditorBordersUseCase.swift b/core/Sources/Components/TextEditor/UseCase/TextEditorBordersUseCase.swift index 1071cabc3..55f0d9508 100644 --- a/core/Sources/Components/TextEditor/UseCase/TextEditorBordersUseCase.swift +++ b/core/Sources/Components/TextEditor/UseCase/TextEditorBordersUseCase.swift @@ -20,13 +20,7 @@ final class TextEditorBordersUseCase: TextEditorBordersUseCasable { isFocused: Bool) -> TextEditorBorders { let radious = theme.border.radius.large - let width: CGFloat - - if intent == .neutral, !isFocused { - width = theme.border.width.small - } else { - width = theme.border.width.medium - } + let width = isFocused ? theme.border.width.medium : theme.border.width.small return .init( radius: radious, diff --git a/core/Sources/Components/TextEditor/UseCase/TextEditorColorsUseCase.swift b/core/Sources/Components/TextEditor/UseCase/TextEditorColorsUseCase.swift index e014f060e..640c9d66b 100644 --- a/core/Sources/Components/TextEditor/UseCase/TextEditorColorsUseCase.swift +++ b/core/Sources/Components/TextEditor/UseCase/TextEditorColorsUseCase.swift @@ -31,7 +31,7 @@ struct TextEditorColorsUseCase: TextEditorColorsUseCasable { let background: any ColorToken if !isEnabled || isReadonly { - let dim = isReadonly ? theme.dims.none : theme.dims.dim3 + let dim = !isEnabled ? theme.dims.dim3 : theme.dims.none text = theme.colors.base.onSurface.opacity(dim) border = theme.colors.base.onSurface.opacity(theme.dims.dim3) background = theme.colors.base.onSurface.opacity(theme.dims.dim5) diff --git a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift index 7d7c89f3c..f7987c92a 100644 --- a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift +++ b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift @@ -8,6 +8,10 @@ import SwiftUI +enum Field: Hashable { + case text +} + public struct TextEditorView: View { @ScaledMetric private var minHeight: CGFloat = 44 @@ -19,7 +23,9 @@ public struct TextEditorView: View { @Binding private var text: String private var titleKey: String - @FocusState private var isFocused: Bool + @FocusState private var focusedField: Field? + @Environment(\.isEnabled) private var isEnabled + @State private var textEditorEnabled: Bool = true private var isPlaceholderTextHidden: Bool { return !self.titleKey.isEmpty && self.text.isEmpty @@ -70,16 +76,27 @@ public struct TextEditorView: View { .border(width: self.viewModel.borderWidth * self.scaleFactor, radius: self.viewModel.borderRadius, colorToken: self.viewModel.borderColor) .tint(self.viewModel.textColor.color) .allowsHitTesting(self.viewModel.isEnabled) - .focused(self.$isFocused) - .onChange(of: self.isFocused) { value in - self.viewModel.isFocused = value + .focused(self.$focusedField, equals: .text) + .onChange(of: self.focusedField) { focusedField in + self.viewModel.isFocused = focusedField == .text } - .isEnabledChanged { isEnabled in + .isEnabled(self.isEnabled) { isEnabled in self.viewModel.isEnabled = isEnabled } + .onChange(of: self.viewModel.isEnabled) { isEnabled in + if !isEnabled { + self.focusedField = nil + } + self.textEditorEnabled = isEnabled + } + .onChange(of: self.viewModel.isReadOnly) { isReadOnly in + if isReadOnly { + self.focusedField = nil + } + } .onTapGesture { if !self.viewModel.isReadOnly { - self.isFocused = true + self.focusedField = .text } } .accessibilityElement() @@ -101,9 +118,9 @@ public struct TextEditorView: View { trailing: self.viewModel.horizontalSpacing - self.defaultTexEditorHorizontalPadding ) ) - .opacity(!self.isPlaceholderHidden || self.isFocused ? 1 : 0) + .opacity(!self.isPlaceholderHidden || self.viewModel.isFocused ? 1 : 0) .accessibilityHidden(true) - + .environment(\.isEnabled, self.textEditorEnabled) } @ViewBuilder @@ -137,3 +154,12 @@ public struct TextEditorView: View { return self } } + +private extension View { + func isEnabled(_ value: Bool, complition: @escaping (Bool) -> Void) -> some View { + DispatchQueue.main.async { + complition(value) + } + return self.disabled(!value) + } +} diff --git a/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift b/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift index 62e331a6f..48b21e8da 100644 --- a/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift +++ b/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift @@ -104,6 +104,9 @@ public final class TextEditorUIView: UITextView { set { self.viewModel.isEnabled = newValue self.isUserInteractionEnabled = newValue + if !isEnabled { + _ = self.resignFirstResponder() + } } } From a63d19e39eaaabaf74c0a7fa3f4411417d8ff3a4 Mon Sep 17 00:00:00 2001 From: Alican Aycil Date: Mon, 15 Jul 2024 16:41:11 +0200 Subject: [PATCH 4/5] [TextView_1048] Fix border test --- .../UseCase/TexEditBorderUseCaseTests.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/core/Sources/Components/TextEditor/UseCase/TexEditBorderUseCaseTests.swift b/core/Sources/Components/TextEditor/UseCase/TexEditBorderUseCaseTests.swift index 575f5ba03..bcd9145b2 100644 --- a/core/Sources/Components/TextEditor/UseCase/TexEditBorderUseCaseTests.swift +++ b/core/Sources/Components/TextEditor/UseCase/TexEditBorderUseCaseTests.swift @@ -18,18 +18,9 @@ final class TextEditorBorderUseCaseTests: XCTestCase { let texteditorBorders = TextEditorBordersUseCase().execute(theme: theme, intent: intent, isFocused: false) - let borderWidth: CGFloat - - if intent == .neutral { - borderWidth = theme.border.width.small - } else { - borderWidth = theme.border.width.medium - } - - let expectedBorders = TextEditorBorders ( radius: theme.border.radius.large, - width: borderWidth + width: theme.border.width.small ) XCTAssertEqual(texteditorBorders, expectedBorders, "Wrong border width") } From 0cb268977fef8ea1e6b852132051807e7bc73972 Mon Sep 17 00:00:00 2001 From: Alican Aycil Date: Wed, 17 Jul 2024 13:35:09 +0200 Subject: [PATCH 5/5] [TextView_1059] Fix text position on readonly mode --- .../View/SwiftUI/TextEditorView.swift | 47 +++++++++---------- .../View/UIKit/TextEditorUIView.swift | 10 ++-- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift index f7987c92a..0fc291a35 100644 --- a/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift +++ b/core/Sources/Components/TextEditor/View/SwiftUI/TextEditorView.swift @@ -10,13 +10,15 @@ import SwiftUI enum Field: Hashable { case text + case none } public struct TextEditorView: View { @ScaledMetric private var minHeight: CGFloat = 44 - @ScaledMetric private var defaultTexEditorVerticalPadding: CGFloat = 9 - @ScaledMetric private var defaultTexEditorHorizontalPadding: CGFloat = 5 + private var defaultTexEditorTopPadding: CGFloat = 8 + private var defaultTexEditorBottomPadding: CGFloat = 9 + private var defaultTexEditorHorizontalPadding: CGFloat = 5 @ScaledMetric private var scaleFactor: CGFloat = 1.0 @ObservedObject private var viewModel: TextEditorViewModel @@ -32,7 +34,11 @@ public struct TextEditorView: View { } private var isPlaceholderHidden: Bool { - return self.isPlaceholderTextHidden || self.viewModel.isReadOnly + if #available(iOS 16.0, *) { + return self.isPlaceholderTextHidden || self.viewModel.isReadOnly + } else { + return self.isPlaceholderTextHidden || self.viewModel.isReadOnly || !self.isEnabled + } } public init( @@ -62,18 +68,11 @@ public struct TextEditorView: View { .scrollIndicators(.never) } else { self.placeHolderView() - .onAppear { - UIScrollView.appearance().showsVerticalScrollIndicator = false - } self.textEditorView() - .onAppear { - UITextView.appearance().backgroundColor = .clear - UITextView.appearance().showsVerticalScrollIndicator = false - } } } .frame(minHeight: self.minHeight) - .border(width: self.viewModel.borderWidth * self.scaleFactor, radius: self.viewModel.borderRadius, colorToken: self.viewModel.borderColor) + .border(width: self.viewModel.borderWidth * self.scaleFactor, radius: self.viewModel.borderRadius * self.scaleFactor, colorToken: self.viewModel.borderColor) .tint(self.viewModel.textColor.color) .allowsHitTesting(self.viewModel.isEnabled) .focused(self.$focusedField, equals: .text) @@ -112,10 +111,10 @@ public struct TextEditorView: View { .foregroundStyle(self.viewModel.textColor.color) .padding( EdgeInsets( - top: .zero, - leading: self.viewModel.horizontalSpacing - self.defaultTexEditorHorizontalPadding, - bottom: .zero, - trailing: self.viewModel.horizontalSpacing - self.defaultTexEditorHorizontalPadding + top: .zero + self.scaleFactor, + leading: (self.viewModel.horizontalSpacing * self.scaleFactor - self.defaultTexEditorHorizontalPadding), + bottom: .zero + self.scaleFactor, + trailing: (self.viewModel.horizontalSpacing * self.scaleFactor - self.defaultTexEditorHorizontalPadding) ) ) .opacity(!self.isPlaceholderHidden || self.viewModel.isFocused ? 1 : 0) @@ -131,21 +130,21 @@ public struct TextEditorView: View { Text(self.isPlaceholderTextHidden ? self.titleKey : self.$text.wrappedValue) .font(self.viewModel.font.font) .foregroundStyle(self.isPlaceholderTextHidden ? self.viewModel.placeholderColor.color : self.viewModel.textColor.color) - .frame(maxWidth: .infinity, alignment: .leading) + .padding( + EdgeInsets( + top: self.defaultTexEditorTopPadding + self.scaleFactor, + leading: self.viewModel.horizontalSpacing * self.scaleFactor, + bottom: self.defaultTexEditorBottomPadding + self.scaleFactor, + trailing: self.viewModel.horizontalSpacing * self.scaleFactor + ) + ) .opacity(self.isPlaceholderHidden ? 1 : 0) .accessibilityHidden(true) + Spacer(minLength: 0) } Spacer(minLength: 0) } - .padding( - EdgeInsets( - top: self.defaultTexEditorVerticalPadding, - leading: self.viewModel.horizontalSpacing, - bottom: self.defaultTexEditorVerticalPadding, - trailing: self.viewModel.horizontalSpacing - ) - ) } } diff --git a/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift b/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift index 48b21e8da..8535347a6 100644 --- a/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift +++ b/core/Sources/Components/TextEditor/View/UIKit/TextEditorUIView.swift @@ -171,21 +171,21 @@ public final class TextEditorUIView: UITextView { self.textContainer.lineFragmentPadding = 0 self.textContainerInset = UIEdgeInsets( top: self.defaultSystemVerticalPadding, - left: self.viewModel.horizontalSpacing, + left: self.viewModel.horizontalSpacing * self.scaleFactor, bottom: self.defaultSystemVerticalPadding, - right: self.viewModel.horizontalSpacing + right: self.viewModel.horizontalSpacing * self.scaleFactor ) self.addSubview(self.placeHolderLabel) self.placeHolderConstarints = [ self.placeHolderLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: self.defaultSystemVerticalPadding), - self.placeHolderLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: self.viewModel.horizontalSpacing), - self.placeHolderLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -self.viewModel.horizontalSpacing), + self.placeHolderLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: self.viewModel.horizontalSpacing * self.scaleFactor), + self.placeHolderLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -self.viewModel.horizontalSpacing * self.scaleFactor), self.placeHolderLabel.bottomAnchor.constraint(greaterThanOrEqualTo: self.bottomAnchor, constant: -self.defaultSystemVerticalPadding) ] self.placeHolderLabelYAnchor = self.placeHolderLabel.centerYAnchor.constraint(lessThanOrEqualTo: self.centerYAnchor) self.placeHolderLabelXAnchor = self.placeHolderLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor) - self.placeholderLabelWidthAnchor = self.placeHolderLabel.widthAnchor.constraint(lessThanOrEqualTo: self.textInputView.widthAnchor, constant: -2 * self.viewModel.horizontalSpacing) + self.placeholderLabelWidthAnchor = self.placeHolderLabel.widthAnchor.constraint(lessThanOrEqualTo: self.textInputView.widthAnchor, constant: -2 * self.viewModel.horizontalSpacing * self.scaleFactor) self.heightAnchor.constraint(greaterThanOrEqualToConstant: self.minHeight).isActive = true }