SwiftUI - 是否可以将 Stack 或 Group 声明为 属性?
SwiftUI - Is it possible to declare a Stack or Group as a property?
我首先要解析一个大文本文件并收集所有必要的视图,然后再最终显示它们。我让它与 AnyView
数组一起工作,但它真的不好,因为它会擦除所有类型。
基本上,我只是想要一个容器来收集里面的视图,直到我最终显示它们。
所以我想知道我是否可以做这样的事情:
class Demo {
var content = VStack()
private func mapInput() {
// ...
}
private func parse() {
for word in mappedInput { // mappedInput is the collection of tags & words that is done before
switch previous {
case "i":
content.add(Text(word).italic())
case "h":
content.add(Text(word).foregroundColor(.green))
case "img":
content.add(Image(word))
}
}
}
}
然后用 VStack
做一些事。但是我收到以下错误:
Error: Generic parameter 'Content' could not be inferred
Explicitly specify the generic arguments to fix this issue
Error: Missing argument for parameter 'content' in call
Insert ', content: <#() -> _#>'
编辑:
我尝试用普通的 ViewBuilder
来代替。这里的问题是现在所有的 Text
看起来都不像一个文本。
struct ViewBuilderDemo: View {
private let exampleInputString =
"""
<i>Welcome.</i><h>Resistance deepens the negative thoughts, acceptance</h><f>This will be bold</f><h>higlight reel</h><f>myappisgood</f>lets go my friend tag parsin in SwiftUI xcode 13 on Mac<img>xcode</img>Mini<f>2020</f><eh>One is beating oneself up, <img>picture</img>the other for looking opportunities. <h>One is a disempowering question, while the other empowers you.</h> Unfortunately, what often comes with the first type of questions is a defensive mindset. You start thinking of others as rivals; you have to ‘fight’ for something so they can't have it, because if one of them gets it then you automatically lose it.
"""
private var mappedInput: [String]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(Array(zip(mappedInput.indices, mappedInput)), id: \.0) { index, word in
if index > 0 {
if !isTag(tag: word) {
let previous = mappedInput[index - 1]
switch previous {
case "i":
Text("\(word) ")
.italic()
.foregroundColor(.gray)
case "h":
Text("\(word) ")
.foregroundColor(.green)
.bold()
case "f":
Text("\(word) ")
.bold()
case "eh":
Divider()
.frame(maxWidth: 200)
.padding(.top, 24)
.padding(.bottom, 24)
case "img":
Image(word)
.resizable()
.scaledToFit()
.frame(width: UIScreen.main.bounds.width * 0.7, height: 150)
default:
Text("\(word) ")
}
}
}
}
}
.padding()
}
}
init() {
let separators = CharacterSet(charactersIn: "<>")
mappedInput = exampleInputString.components(separatedBy: separators).filter{[=12=] != ""}
}
private func isTag(tag currentTag: String) -> Bool {
for tag in Tags.allCases {
if tag.rawValue == currentTag {
return true
}
}
return false
}
enum Tags: String, CaseIterable {
case h = "h"
case hEnd = "/h"
case b = "f"
case bEnd = "/f"
case i = "i"
case iEnd = "/i"
case eh = "eh"
case img = "img"
case imgEnd = "/img"
}
}
所以似乎不可能,Group
、VStack
或类似的东西不应该那样存储。相反,必须以其他方式存储内容,然后在 ViewBuilder
(或只是 body
)中动态重新创建它。
这就是我最终的做法:
解析器分开 class,它使用两个单独的数组。一个用于跟踪内容类型和顺序(例如 1. 文本、2. 图像、3. 分隔符等),然后一个用于 Texts
,因此可以保存所有降价格式以备后用ViewBuilder
.
class TextParserTest {
var inputText: String
var contentStructure: [(contentType: ContentTag, content: String)] = []
var separatedTextObjects = [Text("")]
private var activeTextArrayIndex = 0
private var lastElementWasText = false
private var mappedInput: [String]
init(inputString: String) {
self.inputText = inputString
let newString = inputString.replacingOccurrences(of: "<eh>", with: "<eh>.</eh>")
let separators = CharacterSet(charactersIn: "<>")
mappedInput = newString.components(separatedBy: separators)
parse()
}
private func isTag(word: String) -> Bool {
for tag in RawTags.allCases {
if tag.rawValue == word {
return true
}
}
return false
}
private func parse() {
for (index, word) in mappedInput.enumerated() {
if index > 0 {
if !isTag(word: word) {
let tag = mappedInput[index - 1]
applyStyle(tag: tag, word: word)
}
}
else if (index == 0 && !isTag(word: word)) {
var text = separatedTextObjects[activeTextArrayIndex]
.foregroundColor(.black)
text = text + Text("\(word) ")
separatedTextObjects[activeTextArrayIndex] = text
}
}
if (lastElementWasText) {
contentStructure.append((contentType: ContentTag.text, content: "\(activeTextArrayIndex)"))
}
}
private func applyStyle(tag: String, word: String) {
var text = separatedTextObjects[activeTextArrayIndex]
.foregroundColor(.black)
switch tag {
case "i":
text = text +
Text("\(word)")
.italic()
.foregroundColor(.gray)
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
case "h":
text = text +
Text("\(word)")
.foregroundColor(.accentColor)
.bold()
.kerning(2)
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
case "f":
text = text +
Text("\(word)")
.bold()
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
case "eh":
contentStructure.append((contentType: ContentTag.text, content: "\(activeTextArrayIndex)"))
contentStructure.append((contentType: ContentTag.divider, content: ""))
separatedTextObjects.append(Text(""))
activeTextArrayIndex += 1
lastElementWasText = false
case "img":
contentStructure.append((contentType: ContentTag.text, content: "\(activeTextArrayIndex)"))
contentStructure.append((contentType: ContentTag.image, content: "\(word)"))
separatedTextObjects.append(Text(""))
activeTextArrayIndex += 1
lastElementWasText = false
default:
text = text +
Text("\(word)")
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
}
}
private enum RawTags: String, CaseIterable {
case h = "h"
case hEnd = "/h"
case b = "f"
case bEnd = "/f"
case i = "i"
case iEnd = "/i"
case eh = "eh"
case ehEnd = "/eh"
case img = "img"
case imgEnd = "/img"
}
}
enum ContentTag: String {
case text = "text"
case image = "image"
case divider = "divider"
}
然后像这样与 SwiftUI 一起使用它:
struct ViewBuilderDemo2: View {
private let exampleInputString =
"""
<i>Welcome.</i><h>Resistance deepens the negative thoughts, acceptance</h><f>This will be bold</f><h>higlight reel</h><f>myappisgood</f>lets go my friend tag parsin in SwiftUI xcode 13 on Mac<img>xcode</img>Mini<f>2020</f><eh>One is beating oneself up, <img>picture</img>the other for looking opportunities. <h>One is a disempowering question, while the other empowers you.</h> Unfortunately, what often comes with the first type of questions is a defensive mindset. You start thinking of others as rivals; you have to ‘fight’ for something so they can't have it, because if one of them gets it then you <eh><f>automatically</f> lose it. Kelb kelb text lorem ipsum and more.
"""
var parser: TextParserTest
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(parser.contentStructure.indices, id: \.self) { i in
let contentType = parser.contentStructure[i].contentType
let content = parser.contentStructure[i].content
switch contentType {
case ContentTag.divider:
Divider()
.frame(maxWidth: 200)
.padding(.top, 24)
.padding(.bottom, 24)
case ContentTag.image:
Image(content)
.resizable()
.scaledToFit()
.frame(width: UIScreen.main.bounds.width * 0.7, height: 150)
.padding(.top, 24)
.padding(.bottom, 24)
case ContentTag.text:
let textIndex = Int(content) ?? 0
parser.separatedTextObjects[textIndex]
}
}
}
.padding()
}
}
init() {
parser = TextParserTest(inputString: exampleInputString)
}
}
我首先要解析一个大文本文件并收集所有必要的视图,然后再最终显示它们。我让它与 AnyView
数组一起工作,但它真的不好,因为它会擦除所有类型。
基本上,我只是想要一个容器来收集里面的视图,直到我最终显示它们。
所以我想知道我是否可以做这样的事情:
class Demo {
var content = VStack()
private func mapInput() {
// ...
}
private func parse() {
for word in mappedInput { // mappedInput is the collection of tags & words that is done before
switch previous {
case "i":
content.add(Text(word).italic())
case "h":
content.add(Text(word).foregroundColor(.green))
case "img":
content.add(Image(word))
}
}
}
}
然后用 VStack
做一些事。但是我收到以下错误:
Error: Generic parameter 'Content' could not be inferred
Explicitly specify the generic arguments to fix this issue
Error: Missing argument for parameter 'content' in call
Insert ', content: <#() -> _#>'
编辑:
我尝试用普通的 ViewBuilder
来代替。这里的问题是现在所有的 Text
看起来都不像一个文本。
struct ViewBuilderDemo: View {
private let exampleInputString =
"""
<i>Welcome.</i><h>Resistance deepens the negative thoughts, acceptance</h><f>This will be bold</f><h>higlight reel</h><f>myappisgood</f>lets go my friend tag parsin in SwiftUI xcode 13 on Mac<img>xcode</img>Mini<f>2020</f><eh>One is beating oneself up, <img>picture</img>the other for looking opportunities. <h>One is a disempowering question, while the other empowers you.</h> Unfortunately, what often comes with the first type of questions is a defensive mindset. You start thinking of others as rivals; you have to ‘fight’ for something so they can't have it, because if one of them gets it then you automatically lose it.
"""
private var mappedInput: [String]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(Array(zip(mappedInput.indices, mappedInput)), id: \.0) { index, word in
if index > 0 {
if !isTag(tag: word) {
let previous = mappedInput[index - 1]
switch previous {
case "i":
Text("\(word) ")
.italic()
.foregroundColor(.gray)
case "h":
Text("\(word) ")
.foregroundColor(.green)
.bold()
case "f":
Text("\(word) ")
.bold()
case "eh":
Divider()
.frame(maxWidth: 200)
.padding(.top, 24)
.padding(.bottom, 24)
case "img":
Image(word)
.resizable()
.scaledToFit()
.frame(width: UIScreen.main.bounds.width * 0.7, height: 150)
default:
Text("\(word) ")
}
}
}
}
}
.padding()
}
}
init() {
let separators = CharacterSet(charactersIn: "<>")
mappedInput = exampleInputString.components(separatedBy: separators).filter{[=12=] != ""}
}
private func isTag(tag currentTag: String) -> Bool {
for tag in Tags.allCases {
if tag.rawValue == currentTag {
return true
}
}
return false
}
enum Tags: String, CaseIterable {
case h = "h"
case hEnd = "/h"
case b = "f"
case bEnd = "/f"
case i = "i"
case iEnd = "/i"
case eh = "eh"
case img = "img"
case imgEnd = "/img"
}
}
所以似乎不可能,Group
、VStack
或类似的东西不应该那样存储。相反,必须以其他方式存储内容,然后在 ViewBuilder
(或只是 body
)中动态重新创建它。
这就是我最终的做法:
解析器分开 class,它使用两个单独的数组。一个用于跟踪内容类型和顺序(例如 1. 文本、2. 图像、3. 分隔符等),然后一个用于 Texts
,因此可以保存所有降价格式以备后用ViewBuilder
.
class TextParserTest {
var inputText: String
var contentStructure: [(contentType: ContentTag, content: String)] = []
var separatedTextObjects = [Text("")]
private var activeTextArrayIndex = 0
private var lastElementWasText = false
private var mappedInput: [String]
init(inputString: String) {
self.inputText = inputString
let newString = inputString.replacingOccurrences(of: "<eh>", with: "<eh>.</eh>")
let separators = CharacterSet(charactersIn: "<>")
mappedInput = newString.components(separatedBy: separators)
parse()
}
private func isTag(word: String) -> Bool {
for tag in RawTags.allCases {
if tag.rawValue == word {
return true
}
}
return false
}
private func parse() {
for (index, word) in mappedInput.enumerated() {
if index > 0 {
if !isTag(word: word) {
let tag = mappedInput[index - 1]
applyStyle(tag: tag, word: word)
}
}
else if (index == 0 && !isTag(word: word)) {
var text = separatedTextObjects[activeTextArrayIndex]
.foregroundColor(.black)
text = text + Text("\(word) ")
separatedTextObjects[activeTextArrayIndex] = text
}
}
if (lastElementWasText) {
contentStructure.append((contentType: ContentTag.text, content: "\(activeTextArrayIndex)"))
}
}
private func applyStyle(tag: String, word: String) {
var text = separatedTextObjects[activeTextArrayIndex]
.foregroundColor(.black)
switch tag {
case "i":
text = text +
Text("\(word)")
.italic()
.foregroundColor(.gray)
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
case "h":
text = text +
Text("\(word)")
.foregroundColor(.accentColor)
.bold()
.kerning(2)
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
case "f":
text = text +
Text("\(word)")
.bold()
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
case "eh":
contentStructure.append((contentType: ContentTag.text, content: "\(activeTextArrayIndex)"))
contentStructure.append((contentType: ContentTag.divider, content: ""))
separatedTextObjects.append(Text(""))
activeTextArrayIndex += 1
lastElementWasText = false
case "img":
contentStructure.append((contentType: ContentTag.text, content: "\(activeTextArrayIndex)"))
contentStructure.append((contentType: ContentTag.image, content: "\(word)"))
separatedTextObjects.append(Text(""))
activeTextArrayIndex += 1
lastElementWasText = false
default:
text = text +
Text("\(word)")
separatedTextObjects[activeTextArrayIndex] = text
lastElementWasText = true
}
}
private enum RawTags: String, CaseIterable {
case h = "h"
case hEnd = "/h"
case b = "f"
case bEnd = "/f"
case i = "i"
case iEnd = "/i"
case eh = "eh"
case ehEnd = "/eh"
case img = "img"
case imgEnd = "/img"
}
}
enum ContentTag: String {
case text = "text"
case image = "image"
case divider = "divider"
}
然后像这样与 SwiftUI 一起使用它:
struct ViewBuilderDemo2: View {
private let exampleInputString =
"""
<i>Welcome.</i><h>Resistance deepens the negative thoughts, acceptance</h><f>This will be bold</f><h>higlight reel</h><f>myappisgood</f>lets go my friend tag parsin in SwiftUI xcode 13 on Mac<img>xcode</img>Mini<f>2020</f><eh>One is beating oneself up, <img>picture</img>the other for looking opportunities. <h>One is a disempowering question, while the other empowers you.</h> Unfortunately, what often comes with the first type of questions is a defensive mindset. You start thinking of others as rivals; you have to ‘fight’ for something so they can't have it, because if one of them gets it then you <eh><f>automatically</f> lose it. Kelb kelb text lorem ipsum and more.
"""
var parser: TextParserTest
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(parser.contentStructure.indices, id: \.self) { i in
let contentType = parser.contentStructure[i].contentType
let content = parser.contentStructure[i].content
switch contentType {
case ContentTag.divider:
Divider()
.frame(maxWidth: 200)
.padding(.top, 24)
.padding(.bottom, 24)
case ContentTag.image:
Image(content)
.resizable()
.scaledToFit()
.frame(width: UIScreen.main.bounds.width * 0.7, height: 150)
.padding(.top, 24)
.padding(.bottom, 24)
case ContentTag.text:
let textIndex = Int(content) ?? 0
parser.separatedTextObjects[textIndex]
}
}
}
.padding()
}
}
init() {
parser = TextParserTest(inputString: exampleInputString)
}
}