获取SwiftUI文本中的单词框架
我想找出一个单词在句子中的位置,这样我就可以为这个单词设置一个框架。 例如: 目前是否可以在SwiftUI中执行此操作 我写了一些代码,这就是我得到的获取SwiftUI文本中的单词框架,swiftui,swiftui-text,Swiftui,Swiftui Text,我想找出一个单词在句子中的位置,这样我就可以为这个单词设置一个框架。 例如: 目前是否可以在SwiftUI中执行此操作 我写了一些代码,这就是我得到的 import SwiftUI struct ContentView: View { @State var text = "I usually get ----- around nine o'clock every morning" @State var rects = [CGRect.zero] var body:
import SwiftUI
struct ContentView: View {
@State var text = "I usually get ----- around nine o'clock every morning"
@State var rects = [CGRect.zero]
var body: some View {
ZStack {
TextView(text: $text, rects: $rects)
.overlay(
ForEach(0..<self.rects.count, id: \.self) { index in
RoundedRectangle(cornerRadius: 6)
.frame(width: self.rects[index].size.width, height: self.rects[index].size.height)
.position(self.rects[index].origin)
.foregroundColor(Color.red)
}
)
}
}
}
struct TextView: UIViewRepresentable {
@Binding var text: String
@Binding var rects: [CGRect]
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.isEditable = true
textView.isUserInteractionEnabled = true
textView.font = UIFont.systemFont(ofSize: 24)
DispatchQueue.main.async {
let ranges = self.searchRanges(in: textView.text)
self.rects = self.viewRects(for: ranges, textView: textView)
}
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: TextView
init(_ uiTextView: TextView) {
self.parent = uiTextView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
func searchRanges(in text: String) -> [Range<String.Index>] {
var ranges = [Range<String.Index>]()
var searchRange = text.startIndex ..< text.endIndex
var range = text.range(
of: "-----",
options: .caseInsensitive,
range: searchRange,
locale: nil
)
while let findedRange = range {
ranges.append(findedRange)
searchRange = findedRange.upperBound ..< text.endIndex
range = text.range(
of: "-----",
options: .caseInsensitive,
range: searchRange,
locale: nil
)
}
return ranges
}
func viewRects(for rnges: [Range<String.Index>], textView: UITextView) -> [CGRect] {
var rects = [CGRect]()
for range in rnges {
let upperBound = range.upperBound.utf16Offset(in: textView.text)
let lowerBound = range.lowerBound.utf16Offset(in: textView.text)
let length = upperBound - lowerBound
if let start = textView.position(from: textView.beginningOfDocument, offset: lowerBound),
let end = textView.position(from: start, offset: length),
let txtRange = textView.textRange(from: start, to: end) {
var rect = textView.firstRect(for: txtRange)
rect.origin.x = rect.origin.x
rect.origin.y = rect.midY
rects.append(rect)
}
}
return rects
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
UIKit上的代码与情节提要相同
您可以通过拆分文本,在其上循环,然后使用覆盖来框显所需的单词来实现这一点 请参见此示例:
struct HighlightView: View {
var words: [FramableWord] = []
struct FramableWord: Identifiable {
let id = UUID()
let text: String
let isFramed: Bool
}
func frame(word: String, in text: String) -> [FramableWord] {
return text.split(separator: " ").map(String.init).map {
FramableWord(text: $0, isFramed: $0 == word)
}
}
init() {
words = frame(word: "up", in: "I get up at 9")
}
var body: some View {
HStack(spacing: 2) {
ForEach(words) { word -> AnyView in
if word.isFramed {
return AnyView(
Text(word.text)
.padding(2)
.overlay(RoundedRectangle(cornerRadius: 2).stroke(Color.blue, lineWidth: 2))
)
}
return AnyView(Text(word.text))
}
}
}
}
结果:
您可以通过拆分文本,在其上循环,然后使用覆盖来框显所需的单词来实现这一点 请参见此示例:
struct HighlightView: View {
var words: [FramableWord] = []
struct FramableWord: Identifiable {
let id = UUID()
let text: String
let isFramed: Bool
}
func frame(word: String, in text: String) -> [FramableWord] {
return text.split(separator: " ").map(String.init).map {
FramableWord(text: $0, isFramed: $0 == word)
}
}
init() {
words = frame(word: "up", in: "I get up at 9")
}
var body: some View {
HStack(spacing: 2) {
ForEach(words) { word -> AnyView in
if word.isFramed {
return AnyView(
Text(word.text)
.padding(2)
.overlay(RoundedRectangle(cornerRadius: 2).stroke(Color.blue, lineWidth: 2))
)
}
return AnyView(Text(word.text))
}
}
}
}
结果:
我们无法直接测试您的代码,因为我们没有UITextView。firstRectfor:。但是,有两件事是显而易见的: 您需要在.overlay中使用对齐为:.topLeading的ZStack。如果在RECT中有多个值,并且默认情况下SwiftUI从中心布局,则ForEach将无法作为.overlay的根始终工作。 您需要使用.offset而不是.position。“位置”用于框架的中心。 结构ContentView:View{ 变量rects:[CGRect]=[ 零 CGRectx:10,y:10,宽度:10,高度:50, CGRectx:20,y:20,宽度:10,高度:50, CGRectx:30,y:30,宽度:10,高度:50 ] func sizefor rect:CGRect->CGSize{ CGSizewidth:rect.minX,高度:rect.minY } var body:一些观点{ 颜色。蓝色。不透明度0.125 .框架宽度:200,高度:400 覆盖 ZStackalignment:。顶部引导{ Color.clear//最大化ZStack的大小
ForEach0..我们无法直接测试您的代码,因为我们没有UITextView.firstRectfor:。但是,有两件事是显而易见的: 您需要在.overlay中使用对齐方式为:.topLeading的ZStack。如果在rects中有多个值,并且默认情况下SwiftUI从中心布局,则ForEach将无法作为.overlay的根始终工作。 您需要使用.offset而不是.position。position用于帧的中心。 结构ContentView:View{ 变量rects:[CGRect]=[ 零 CGRectx:10,y:10,宽度:10,高度:50, CGRectx:20,y:20,宽度:10,高度:50, CGRectx:30,y:30,宽度:10,高度:50 ] func sizefor rect:CGRect->CGSize{ CGSizewidth:rect.minX,高度:rect.minY } var body:一些观点{ 颜色。蓝色。不透明度0.125 .框架宽度:200,高度:400 覆盖 ZStackalignment:。顶部引导{ Color.clear//最大化ZStack的大小
在工作了几个小时后,我找到了一个解决方案,这就是它的样子
import SwiftUI
struct TextView: View {
@State var text = ""
@State var gapText = ""
@State var rects = [CGRect.zero]
@State var pattern: String = "-----"
var body: some View {
ZStack {
Representable(text: $text, rects: $rects, pattern: $pattern)
ForEach(0..<self.rects.count, id: \.self) { index in
ZStack {
Button(action: {
}) {
Text(self.gapText)
.foregroundColor(Color("SMTitle"))
.font(.system(size: 30, weight: .medium))
.multilineTextAlignment(.center)
.padding()
}
.frame(width: self.rects[index].size.width, height: self.rects[index].size.height)
.background(Color.white, alignment: .center)
.position(x: self.rects[index].origin.x, y: self.rects[index].origin.y)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Color("SMTitle"), lineWidth: 2)
.frame(width: self.rects[index].size.width, height: self.rects[index].size.height)
.position(x: self.rects[index].origin.x, y: self.rects[index].origin.y)
)
}
}
}
}
}
struct Representable: UIViewRepresentable {
@Binding var text: String
@Binding var rects: [CGRect]
@Binding var pattern: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.delegate = context.coordinator
view.isEditable = false
view.isUserInteractionEnabled = true
view.textColor = UIColor(red: 0.325, green: 0.207, blue: 0.325, alpha: 1)
view.font = UIFont.systemFont(ofSize: 30, weight: .medium)
DispatchQueue.main.async {
let ranges = self.searchRanges(in: view.text)
self.rects = self.viewRects(for: ranges, textView: view)
}
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: Representable
init(_ uiTextView: Representable) {
self.parent = uiTextView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
func searchRanges(in text: String) -> [Range<String.Index>] {
var ranges = [Range<String.Index>]()
var searchRange = text.startIndex ..< text.endIndex
var range = text.range(
of: self.pattern,
options: .caseInsensitive,
range: searchRange,
locale: nil
)
while let findedRange = range {
ranges.append(findedRange)
searchRange = findedRange.upperBound ..< text.endIndex
range = text.range(
of: self.pattern,
options: .caseInsensitive,
range: searchRange,
locale: nil
)
}
return ranges
}
func viewRects(for ranges: [Range<String.Index>], textView: UITextView) -> [CGRect] {
var rects = [CGRect]()
for range in ranges {
let upperBound = range.upperBound.utf16Offset(in: textView.text)
let lowerBound = range.lowerBound.utf16Offset(in: textView.text)
let length = upperBound - lowerBound
if let start = textView.position(from: textView.beginningOfDocument, offset: lowerBound),
let end = textView.position(from: start, offset: length),
let txtRange = textView.textRange(from: start, to: end) {
var rect = textView.firstRect(for: txtRange)
rect.origin.x = rect.midX
rect.origin.y = rect.midY
rect.size.height = rect.size.height
rects.append(rect)
}
}
return rects
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
TextView(text: "I usually get ----- around nine o'clock every morning", gapText: "up")
}
}
我想看到一个更优雅的解决方案在工作了几个小时后,我找到了一个解决方案,这就是它的外观
import SwiftUI
struct TextView: View {
@State var text = ""
@State var gapText = ""
@State var rects = [CGRect.zero]
@State var pattern: String = "-----"
var body: some View {
ZStack {
Representable(text: $text, rects: $rects, pattern: $pattern)
ForEach(0..<self.rects.count, id: \.self) { index in
ZStack {
Button(action: {
}) {
Text(self.gapText)
.foregroundColor(Color("SMTitle"))
.font(.system(size: 30, weight: .medium))
.multilineTextAlignment(.center)
.padding()
}
.frame(width: self.rects[index].size.width, height: self.rects[index].size.height)
.background(Color.white, alignment: .center)
.position(x: self.rects[index].origin.x, y: self.rects[index].origin.y)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Color("SMTitle"), lineWidth: 2)
.frame(width: self.rects[index].size.width, height: self.rects[index].size.height)
.position(x: self.rects[index].origin.x, y: self.rects[index].origin.y)
)
}
}
}
}
}
struct Representable: UIViewRepresentable {
@Binding var text: String
@Binding var rects: [CGRect]
@Binding var pattern: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.delegate = context.coordinator
view.isEditable = false
view.isUserInteractionEnabled = true
view.textColor = UIColor(red: 0.325, green: 0.207, blue: 0.325, alpha: 1)
view.font = UIFont.systemFont(ofSize: 30, weight: .medium)
DispatchQueue.main.async {
let ranges = self.searchRanges(in: view.text)
self.rects = self.viewRects(for: ranges, textView: view)
}
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: Representable
init(_ uiTextView: Representable) {
self.parent = uiTextView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
func searchRanges(in text: String) -> [Range<String.Index>] {
var ranges = [Range<String.Index>]()
var searchRange = text.startIndex ..< text.endIndex
var range = text.range(
of: self.pattern,
options: .caseInsensitive,
range: searchRange,
locale: nil
)
while let findedRange = range {
ranges.append(findedRange)
searchRange = findedRange.upperBound ..< text.endIndex
range = text.range(
of: self.pattern,
options: .caseInsensitive,
range: searchRange,
locale: nil
)
}
return ranges
}
func viewRects(for ranges: [Range<String.Index>], textView: UITextView) -> [CGRect] {
var rects = [CGRect]()
for range in ranges {
let upperBound = range.upperBound.utf16Offset(in: textView.text)
let lowerBound = range.lowerBound.utf16Offset(in: textView.text)
let length = upperBound - lowerBound
if let start = textView.position(from: textView.beginningOfDocument, offset: lowerBound),
let end = textView.position(from: start, offset: length),
let txtRange = textView.textRange(from: start, to: end) {
var rect = textView.firstRect(for: txtRange)
rect.origin.x = rect.midX
rect.origin.y = rect.midY
rect.size.height = rect.size.height
rects.append(rect)
}
}
return rects
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
TextView(text: "I usually get ----- around nine o'clock every morning", gapText: "up")
}
}
我希望看到一个更优雅的解决方案如果您可以结合使用多行,则该解决方案无法正常工作:如果您可以结合使用多行,则该解决方案无法正常工作:您是否尝试过我的解决方案?如果您只需替换Color.blu,我相信它将与您现有的代码一起工作在您的原始代码中使用TextView。您的主要问题是通过添加midX和midY来使用您正在调整的位置。您也没有在ZStack中使用对齐:.topLeading。这基本上可以转换为UIKit坐标。您尝试过我的解决方案吗?我相信如果您只替换颜色,它将适用于您现有的代码.blue,在原始代码中使用TextView。您的主要问题是通过添加midX和midY来使用正在调整的位置。您也没有在ZStack中使用对齐:.topLeading。这基本上可以转换为UIKit坐标。