2

Does anyone know if there is any alternative to doing what this question asks in SwiftUI? Apparently, NSAttributedStrings are not currently supported, and the library that the accepted answer references (FRHyperLabel) is now outdated and does not work for SwiftUI and Swift 5 (or Swift 4, or Swift 3...). I pretty much need the exact functionality that the linked question asked for – that is, the ability to support text with multiple inline buttons, the amount of which vary in different situations. Any ideas?

EDIT: Okay, I'm now about 90% of the way there. This is definitely not an elegant solution (and it still has its issues anyway), so I welcome any suggestions on how to optimize it.

I wrote this code (xcode 11.7, ios 13):

import Foundation
import SwiftUI

struct StackoverflowExample: View {
    var body: some View {
        formattedUsingStruct(raw: "Lorem ipsum _dolor_ sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et _dolore_ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure _dolor_ in reprehenderit in voluptate velit esse cillum _dolore_ eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", buttonText: "dolor")
                .foregroundColor(.primary)
                .font(.system(.body, design: .serif))
                .padding(.horizontal)
    }
    
    // this generates the example that does not work (separated vertically)
    private func formattedUsingStruct(raw: String, buttonText: String) -> some View {
        // label, b or t (button or text), i or n (italic or not)
        var labels: [Label] = []
        let tokens = raw.components(separatedBy: "_")
        for (index, token) in tokens.enumerated() {
            if index % 2 != 0 { // italic
                if token == buttonText {
                    labels.append(Label(text: token, style: "b", italic: true))
                } else {
                    labels.append(Label(text: token, style: "t", italic: true))
                }
            } else {
                labels.append(Label(text: token, style: "t", italic: false))
            }
        }
        
        return VStack(alignment: .leading, spacing: 0) { // the VStack is what is causing the problem. But I don't know what to use in place of it.
            ForEach(labels) { label in
                if label.style == "t" {
                    self.getText(with: label)
                } else {
                    self.getButton(with: label)
                }
            }
        }
    }
    
    private func getButton(with label: Label) -> Button<Text> {
        if label.italic {
            return Button(action: { }, label: { Text(label.text).italic().foregroundColor(.blue) })
        } else {
            return Button(action: { }, label: { Text(label.text).foregroundColor(.blue) })
        }
    }
    
    private func getText(with label: Label) -> Text {
        if label.italic {
            return Text(label.text).italic()
        } else {
            return Text(label.text)
        }
    }
    
    // this generates the example that does work (everything is inline). However, buttons are not supported (just blue text instead).
    private func formatted(raw: String, buttonText: String) -> Text {
        var texts: [Text] = []
        let tokens = raw.components(separatedBy: "_")
        for (index, token) in tokens.enumerated() {
            if index % 2 != 0 { // italic
                if token == buttonText {
                    texts.append(Text(token).italic().foregroundColor(.blue)) // TODO: - add button functionality to click on food and get short description
                } else {
                    texts.append((Text(token).italic()))
                }
            } else {
                texts.append(Text(token))
            }
        }
        return texts.reduce(Text(""), +)
    }
}

struct Label: Hashable, Identifiable {
    var id = UUID()
    var text: String
    var style: String
    var italic: Bool
}

struct StackoverflowExample_Previews: PreviewProvider {
    static var previews: some View {
        StackoverflowExample()
    }
}

Essentially, the code italicizes every instance of either "dolor" or "dolore" (surrounded with the "_" character in the raw text) and then turns the "dolor"s into a button as well. Running it gives me this:

enter image description here

However, I would like all the text and buttons to be in-line with each other, like this (generated using the formatted() method, but buttons are not supported).

enter image description here

I have identified the problem to be the fact that I am returning a VStack in the formattedWithStruct() method. I only did this temporarily, because I wasn't sure what to return if it wasn't a VStack, HStack, or ZStack. Does anyone know how to fix this?

r829
  • 161
  • 1
  • 10

0 Answers0