diff --git a/Package.swift b/Package.swift index 3384cfa..844621a 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,6 @@ let package = Package( name: "TextView", platforms: [ .iOS(.v13), - .macOS(.v10_15), .tvOS(.v13) ], products: [ diff --git a/README.md b/README.md index 35178f3..92b1b77 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,15 @@ - `isEditing: Binding` - The `TextView` will modify the value when it is selected and deselected - You can also modify this value to automatically select and deselect the `TextView` +- `placeholder: String? = nil` - `textAlignment: TextView.TextAlignment = .left` +- `placeholderAlignment: Alignment = .topLeading` +- `placeholderHorizontalPadding: CGFloat = 4.5` +- `placeholderVerticalPadding: CGFloat = 7` - `font: UIFont = .preferredFont(forTextStyle: .body)` - By default, the font is a body-style font - `textColor: UIColor = .black` +- `placeholderColor: Color = .gray` - `backgroundColor: UIColor = .white` - `contentType: TextView.ContentType? = nil` - For semantic purposes only @@ -48,7 +53,11 @@ struct ContentView: View { }) { Text("\(isEditing ? "Stop" : "Start") editing") } - TextView(text: $input, isEditing: $isEditing) + TextView( + text: $input, + isEditing: $isEditing, + placeholder: "Enter text here" + ) } } } diff --git a/Sources/TextView/TextView.swift b/Sources/TextView/TextView.swift index 6aab5f1..5234cfd 100644 --- a/Sources/TextView/TextView.swift +++ b/Sources/TextView/TextView.swift @@ -1,46 +1,134 @@ +#if !os(macOS) + import SwiftUI @available(iOS 13.0, *) -public struct TextView: UIViewRepresentable { - public typealias TextAlignment = NSTextAlignment - public typealias ContentType = UITextContentType - public typealias Autocorrection = UITextAutocorrectionType - public typealias Autocapitalization = UITextAutocapitalizationType - - public final class Coordinator: NSObject, UITextViewDelegate { - private let parent: TextView - - public init(_ parent: TextView) { - self.parent = parent +public struct TextView: View { + public struct Representable: UIViewRepresentable { + public final class Coordinator: NSObject, UITextViewDelegate { + private let parent: Representable + + public init(_ parent: Representable) { + self.parent = parent + } + + private func setIsEditing(to value: Bool) { + DispatchQueue.main.async { + self.parent.isEditing = value + } + } + + public func textViewDidChange(_ textView: UITextView) { + parent.text = textView.text + } + + public func textViewDidBeginEditing(_: UITextView) { + setIsEditing(to: true) + } + + public func textViewDidEndEditing(_: UITextView) { + setIsEditing(to: false) + } } - private func setIsEditing(to value: Bool) { - DispatchQueue.main.async { - self.parent.isEditing = value - } + @Binding private var text: String + @Binding private var isEditing: Bool + + private let textAlignment: TextAlignment + private let font: UIFont + private let textColor: UIColor + private let backgroundColor: UIColor + private let contentType: ContentType? + private let autocorrection: Autocorrection + private let autocapitalization: Autocapitalization + private let isSecure: Bool + private let isEditable: Bool + private let isSelectable: Bool + private let isScrollingEnabled: Bool + private let isUserInteractionEnabled: Bool + + public init( + text: Binding, + isEditing: Binding, + textAlignment: TextAlignment, + font: UIFont, + textColor: UIColor, + backgroundColor: UIColor, + contentType: ContentType?, + autocorrection: Autocorrection, + autocapitalization: Autocapitalization, + isSecure: Bool, + isEditable: Bool, + isSelectable: Bool, + isScrollingEnabled: Bool, + isUserInteractionEnabled: Bool + ) { + _text = text + _isEditing = isEditing + + self.textAlignment = textAlignment + self.font = font + self.textColor = textColor + self.backgroundColor = backgroundColor + self.contentType = contentType + self.autocorrection = autocorrection + self.autocapitalization = autocapitalization + self.isSecure = isSecure + self.isEditable = isEditable + self.isSelectable = isSelectable + self.isScrollingEnabled = isScrollingEnabled + self.isUserInteractionEnabled = isUserInteractionEnabled } - public func textViewDidChange(_ textView: UITextView) { - parent.text = textView.text + public func makeCoordinator() -> Coordinator { + .init(self) } - public func textViewDidBeginEditing(_: UITextView) { - setIsEditing(to: true) + public func makeUIView(context: Context) -> UITextView { + let textView = UITextView() + textView.delegate = context.coordinator + return textView } - public func textViewDidEndEditing(_: UITextView) { - setIsEditing(to: false) + public func updateUIView(_ textView: UITextView, context _: Context) { + textView.text = text + textView.textAlignment = textAlignment + textView.font = font + textView.textColor = textColor + textView.backgroundColor = backgroundColor + textView.textContentType = contentType + textView.autocorrectionType = autocorrection + textView.autocapitalizationType = autocapitalization + textView.isSecureTextEntry = isSecure + textView.isEditable = isEditable + textView.isSelectable = isSelectable + textView.isScrollEnabled = isScrollingEnabled + textView.isUserInteractionEnabled = isUserInteractionEnabled + + _ = isEditing + ? textView.becomeFirstResponder() + : textView.resignFirstResponder() } } + public typealias TextAlignment = NSTextAlignment + public typealias ContentType = UITextContentType + public typealias Autocorrection = UITextAutocorrectionType + public typealias Autocapitalization = UITextAutocapitalizationType + public static let defaultFont = UIFont.preferredFont(forTextStyle: .body) @Binding private var text: String @Binding private var isEditing: Bool + private let placeholder: String? private let textAlignment: TextAlignment + private let placeholderAlignment: Alignment + private let placeholderHorizontalPadding: CGFloat + private let placeholderVerticalPadding: CGFloat private let font: UIFont private let textColor: UIColor + private let placeholderColor: Color private let backgroundColor: UIColor private let contentType: ContentType? private let autocorrection: Autocorrection @@ -54,9 +142,14 @@ public struct TextView: UIViewRepresentable { public init( text: Binding, isEditing: Binding, + placeholder: String? = nil, textAlignment: TextAlignment = .left, + placeholderAlignment: Alignment = .topLeading, + placeholderHorizontalPadding: CGFloat = 4.5, + placeholderVerticalPadding: CGFloat = 7, font: UIFont = Self.defaultFont, textColor: UIColor = .black, + placeholderColor: Color = .gray, backgroundColor: UIColor = .white, contentType: ContentType? = nil, autocorrection: Autocorrection = .default, @@ -69,9 +162,15 @@ public struct TextView: UIViewRepresentable { ) { _text = text _isEditing = isEditing + + self.placeholder = placeholder self.textAlignment = textAlignment + self.placeholderAlignment = placeholderAlignment + self.placeholderHorizontalPadding = placeholderHorizontalPadding + self.placeholderVerticalPadding = placeholderVerticalPadding self.font = font self.textColor = textColor + self.placeholderColor = placeholderColor self.backgroundColor = backgroundColor self.contentType = contentType self.autocorrection = autocorrection @@ -83,32 +182,49 @@ public struct TextView: UIViewRepresentable { self.isUserInteractionEnabled = isUserInteractionEnabled } - public func makeCoordinator() -> Coordinator { - .init(self) + private var _placeholder: String? { + text.isEmpty ? placeholder : nil } - public func makeUIView(context: Context) -> UITextView { - let textView = UITextView() - textView.delegate = context.coordinator - textView.textAlignment = textAlignment - textView.font = font - textView.textColor = textColor - textView.backgroundColor = backgroundColor - textView.textContentType = contentType - textView.autocorrectionType = autocorrection - textView.autocapitalizationType = autocapitalization - textView.isSecureTextEntry = isSecure - textView.isEditable = isEditable - textView.isSelectable = isSelectable - textView.isScrollEnabled = isScrollingEnabled - textView.isUserInteractionEnabled = isUserInteractionEnabled - return textView + private var representable: Representable { + .init( + text: $text, + isEditing: $isEditing, + textAlignment: textAlignment, + font: font, + textColor: textColor, + backgroundColor: backgroundColor, + contentType: contentType, + autocorrection: autocorrection, + autocapitalization: autocapitalization, + isSecure: isSecure, + isEditable: isEditable, + isSelectable: isSelectable, + isScrollingEnabled: isScrollingEnabled, + isUserInteractionEnabled: isUserInteractionEnabled + ) } - public func updateUIView(_ textView: UITextView, context _: Context) { - textView.text = text - _ = isEditing - ? textView.becomeFirstResponder() - : textView.resignFirstResponder() + public var body: some View { + GeometryReader { geometry in + ZStack { + self.representable + self._placeholder.map { placeholder in + Text(placeholder) + .font(.init(self.font)) + .foregroundColor(self.placeholderColor) + .padding(.horizontal, self.placeholderHorizontalPadding) + .padding(.vertical, self.placeholderVerticalPadding) + .frame( + width: geometry.size.width, + height: geometry.size.height, + alignment: self.placeholderAlignment + ) + .allowsHitTesting(false) + } + } + } } } + +#endif