【问题标题】:SwiftUI - Is it possible to declare a Stack or Group as a property?SwiftUI - 是否可以将 Stack 或 Group 声明为属性?
【发布时间】:2021-12-19 11:44:55
【问题描述】:

我首先要解析一个大文本文件并收集所有必要的视图,然后才能最终显示它们。我让它与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 做一些事情。但我收到以下错误:

错误:无法推断通用参数“内容”
显式指定通用参数以解决此问题

错误:调用中的参数“内容”缺少参数
插入',内容: _#>'


编辑:

我尝试使用普通的ViewBuilder 来代替。这里的问题是它现在都是独立的Texts,看起来不像一个文本。

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{$0 != ""}
    }
    
    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"
    }
}

【问题讨论】:

  • 那样不行,解析和准备模型,然后根据模型的种类有条件地在body中构造视图。
  • 而且,从你放在那里的内容来看,你会想在你的模型中使用“AttributedString”,你可以在以后使用它时控制字符串的外观,至少对于你的文本部分正在显示。见AttributedString documentation
  • @Yrb 是的,问题是文本之间可以是分隔符和图像,所以我有时必须将文本分成几部分,这使得 ViewBuilder 几乎不可能。所以我一直在使用AnyView 类型的数组以必要的顺序收集所有的东西,然后显示它。我真的不知道如何做得更好。
  • 如果您展示了您想要展示的模型部分,将会有所帮助。你真的没有问你想回答的问题。我会尽可能以Minimal, Reproducible Example 开头一个新问题。
  • @Yrb 我实际上在这里问过这个问题:stackoverflow.com/questions/69800340。所以这里的这个问题就像是在尝试了多种不同的事情之后的后续。

标签: swiftui properties vstack


【解决方案1】:

所以似乎不可能,GroupVStack 或类似的东西不应该这样存储。相反,必须以其他方式存储内容,然后在 ViewBuilder(或只是 body)中动态地重新创建它。


这就是我最终这样做的方式:

解析器的独立类,它使用两个独立的数组。一个用于跟踪内容类型和顺序(例如 1. 文本、2. 图像、3. 分隔线等),然后一个用于 Texts,因此所有降价格式都可以保存以供以后在 @ 中使用987654326@.

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)
    }
}

【讨论】:

    猜你喜欢
    • 2015-06-16
    • 2016-09-22
    • 1970-01-01
    • 1970-01-01
    • 2014-12-05
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多