当我创建文本视图时:
文本(“ Hello World”)
长按时,我不允许用户选择文本。
我看过使用TextField的情况,但这似乎不允许关闭文本编辑。
我只希望能够显示正文,并允许用户使用系统文本选择器突出显示选择内容。
谢谢!
答案 0 :(得分:13)
使用TextField("", text: .constant("Some text"))
有两个问题:
cut
,paste
和其他可以更改文本的项目中轻按,而无需使用{{1 }} 我对这个问题的解决方案涉及子类化.constant(...)
并使用UITextField
在UIViewRepresentable
和UIKit
之间架桥。
最后,我提供了完整的代码,可将其复制并粘贴到macOS 10.14上的Xcode 11.3中
将SwiftUI
子类化:
UITextField
使用/// This subclass is needed since we want to customize the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {
/// (Not used for this workaround, see below for the full code) Binding from the `CustomTextField` so changes of the text can be observed by `SwiftUI`
fileprivate var _textBinding: Binding<String>!
/// If it is `true` the text field behaves normally.
/// If it is `false` the text cannot be modified only selected, copied and so on.
fileprivate var _isEditable = true {
didSet {
// set the input view so the keyboard does not show up if it is edited
self.inputView = self._isEditable ? nil : UIView()
// do not show autocorrection if it is not editable
self.autocorrectionType = self._isEditable ? .default : .no
}
}
// change the cursor to have zero size
override func caretRect(for position: UITextPosition) -> CGRect {
return self._isEditable ? super.caretRect(for: position) : .zero
}
// override this method to customize the displayed items of 'UIMenuController' (the context menu when selecting text)
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// disable 'cut', 'delete', 'paste','_promptForReplace:'
// if it is not editable
if (!_isEditable) {
switch action {
case #selector(cut(_:)),
#selector(delete(_:)),
#selector(paste(_:)):
return false
default:
// do not show 'Replace...' which can also replace text
// Note: This selector is private and may change
if (action == Selector("_promptForReplace:")) {
return false
}
}
}
return super.canPerformAction(action, withSender: sender)
}
// === UITextFieldDelegate methods
func textFieldDidChangeSelection(_ textField: UITextField) {
// update the text of the binding
self._textBinding.wrappedValue = textField.text ?? ""
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Allow changing the text depending on `self._isEditable`
return self._isEditable
}
}
来实现UIViewRepresentable
SelectableText
在下面的完整代码中,我还实现了struct SelectableText: UIViewRepresentable {
private var text: String
private var selectable: Bool
init(_ text: String, selectable: Bool = true) {
self.text = text
self.selectable = selectable
}
func makeUIView(context: Context) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textField
}
func updateUIView(_ uiView: CustomUITextField, context: Context) {
uiView.text = self.text
uiView._textBinding = .constant(self.text)
uiView._isEditable = false
uiView.isEnabled = self.selectable
}
func selectable(_ selectable: Bool) -> SelectableText {
return SelectableText(self.text, selectable: selectable)
}
}
,可以关闭编辑但仍可以选择编辑。
游乐场视图
代码
CustomTextField
答案 1 :(得分:5)
我发现一个简单的解决方法是只使用上下文菜单:
Text($someText)
.contextMenu(ContextMenu(menuItems: {
Button("Copy", action: {
UIPasteboard.general.string = someText
})
}))
答案 2 :(得分:4)
我遇到了类似的问题,我本质上希望在不进行编辑的情况下选择文本。就我而言,我想在点按文本时显示UIMenuController,而不允许编辑文本或显示光标或键盘。基于先前的答案:
import SwiftUI
import UIKit
struct SelectableText: UIViewRepresentable {
var text: String
@Binding var isSelected: Bool
func makeUIView(context: Context) -> SelectableLabel {
let label = SelectableLabel()
label.textColor = .white
label.font = .systemFont(ofSize: 60, weight: .light)
label.minimumScaleFactor = 0.6
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .right
label.numberOfLines = 1
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label.text = text
return label
}
func updateUIView(_ uiView: SelectableLabel, context: Context) {
uiView.text = text
if isSelected {
uiView.showMenu()
} else {
let _ = uiView.resignFirstResponder()
}
}
}
class SelectableLabel: UILabel {
override var canBecomeFirstResponder: Bool {
return true
}
override init(frame: CGRect) {
super.init(frame: .zero)
highlightedTextColor = .gray
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(copy(_:)), #selector(paste(_:)), #selector(delete(_:)):
return true
default:
return super.canPerformAction(action, withSender: sender)
}
}
override func copy(_ sender: Any?) {
UIPasteboard.general.string = self.stringValue
}
override func paste(_ sender: Any?) {
guard let string = UIPasteboard.general.string else { return }
NotificationCenter.default.post(name: Notification.Name.Paste, object: nil, userInfo: [Keys.PastedString: string])
}
override func delete(_ sender: Any?) {
NotificationCenter.default.post(name: Notification.Name.Delete, object: nil)
}
override func resignFirstResponder() -> Bool {
isHighlighted = false
return super.resignFirstResponder()
}
public func showMenu() {
becomeFirstResponder()
isHighlighted = true
let menu = UIMenuController.shared
menu.showMenu(from: self, rect: bounds)
}
}
我使用自定义粘贴和删除通知来向我的模型对象发送消息,在该对象中处理粘贴和删除操作以适当地更新显示,这对我而言是可行的。绑定也可以使用。
要使用:
SelectableText(text: text, isSelected: self.$isSelected)
.onTapGesture {
self.isSelected.toggle()
}
.onReceive(NotificationCenter.default.publisher(for: UIMenuController.willHideMenuNotification)) { _ in
self.isSelected = false
}
答案 3 :(得分:0)
尝试一下:
TextField("Unused Placeholder", text: .constant("Non-editable text"))
它是不可编辑的,可选的,但不幸的是它显示了一个光标。
答案 4 :(得分:0)
或者,当我们想在不允许编辑的情况下显示文本的“复制”工具提示时,我们可以使用类似的方法。
作为好处,我们将有可能使用原生视图“Text”,这让我们有机会使用原生方法“.font()”、“.foregroundColor()”等。我们也可以将它用于视图组,例如单元格。
以先前的答案为基础。
游乐场代码
import PlaygroundSupport
import SwiftUI
private class SelectableUIView: UIView {
var text: String?
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
func setup() {
self.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu)))
}
@objc func showMenu(_ recognizer: UILongPressGestureRecognizer) {
becomeFirstResponder()
let menu = UIMenuController.shared
if !menu.isMenuVisible {
menu.showMenu(from: self, rect: frame)
}
}
override func copy(_ sender: Any?) {
let board = UIPasteboard.general
board.string = text
UIMenuController.shared.hideMenu()
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(UIResponderStandardEditActions.copy)
}
}
struct SelectableView: UIViewRepresentable {
var text: String
func makeUIView(context: Context) -> UIView {
let view = SelectableUIView()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let view = uiView as? SelectableUIView else {
return
}
view.text = text
}
}
struct SelectableContainer<Content: View>: View {
private let content: () -> Content
private var text: String
public init(text: String, @ViewBuilder content: @escaping () -> Content) {
self.text = text
self.content = content
}
public var body: some View {
ZStack {
content()
SelectableView(text: text)
.layoutPriority(-1)
}
}
}
struct SelectableText: View {
private var text: String
public init(_ text: String) {
self.text = text
}
public var body: some View {
ZStack {
Text(text)
SelectableView(text: text)
.layoutPriority(-1)
}
}
}
struct TextTestView: View {
@State private var text = "text"
var body: some View {
VStack {
SelectableContainer(text: text) {
VStack(alignment: .leading) {
Text("Header")
.font(.body)
Text(text)
.background(Color.orange)
}
}
.background(Color.yellow)
SelectableText(text)
.background(Color.black)
.foregroundColor(.white)
.font(.largeTitle)
}.padding()
}
}
let viewController = UIHostingController(rootView: TextTestView())
viewController.view.frame = CGRect(x: 0, y: 0, width: 400, height: 200)
PlaygroundPage.current.liveView = viewController.view
游乐场视图 Playground view