Learn Courses My Dashboard

Stacks and Texts

How can I make the texts in the for-loop a horisontal sentence. I’m doing this to use .onTapGesture with different URL’s on the different texts.
Appended a screen with the sentences. Thanks :slight_smile:

It might be easier to use a diagram to explain what you are trying to achieve. It would also help if you pasted your ContentView code in a reply.

Code formatting:
Place 3 back-ticks ``` on the line above your code and the line below your code to format it nicely in your reply. The back-ticks create a code block.

I mean copy the code and then paste it in a reply as text but format it as I suggested in my reply. That way I can plug it into a View and see what it is doing and perhaps make a suggestion on what you need to do.

You can do it like this:

import SwiftUI

struct ContentView: View {

    var body: some View {
        HStack {
            HStack(alignment: .top, spacing: 10) {
                let awords = ["Second", "sentence", "to", "the", "left."]
                awords.reduce(Text("")) { $0 + Text($1) + Text(" ")}
                    .fixedSize(horizontal: false, vertical: true)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            HStack(alignment: .top, spacing: 10) {
                let bwords = ["Second", "sentence", "to", "the", "right."]
                bwords.reduce(Text("")) { $0 + Text($1) + Text(" ")}
                    .fixedSize(horizontal: false, vertical: true)
                    .frame(maxWidth: .infinity, alignment: .leading)

            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

If my understanding is correct, I think what @mlp wants to do is place an onTapGesture on each word to go to a different URL in each case so to do that you will need each word to be independently tappable.

This was the only way I could get the sentence to look like a sentence and fit on the screen and be independently tappable.

 HStack(spacing: 0) {
     HStack(alignment: .top, spacing: 3) {
         let awords = ["First", "sentence", "to", "the", "left."]
         ForEach(awords, id: \.self) { aword in
             Text(aword)
                 .onTapGesture {
                     print(aword)
                 }
         }
     }
     HStack(alignment: .top, spacing: 3) {
         let bwords = ["Second", "sentence", "to", "the", "right."]
         ForEach(bwords, id: \.self) { bword in
             Text(bword)
                 .onTapGesture {
                     print(bword)
                 }
         }
     }
 }

For what it is worth, the other issue is that for a smaller device there is restricted space to fit words across the screen without reducing the text size.

I started with Swift in january and this is the first time I’m on the community with a problem. You describe my challenge correctly (thank you), and I will try your solution and tell how it went).
Kind regards :slight_smile:

Thank you for your suggestion. This is my first question on the community, and I wasn’t quite clear with that each word has a different URL. Look below. Thanks again :slight_smile:

import SwiftUI

struct ContentView : View {
    var body: some View {
        HStack {
            VStack (alignment: .leading)  {
    
                HStack(alignment: .top) {
                    Text(verbatim: "First sentence to the left.")
                        .fixedSize(horizontal: false, vertical: true)
                        .frame(maxWidth: .infinity, alignment: .leading)
                    Text(verbatim: "First sentence to the right.")
                        .fixedSize(horizontal: false, vertical: true)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                HStack(spacing: 10) {
                    HStack(alignment: .top, spacing: 10) {
                        let awords = ["Second", "sentence", "to", "the", "left."]
                            ForEach(awords, id: \.self) { aword in
                                Text(aword)
                            }
                    }
                    HStack(alignment: .top, spacing: 10) {
                        let bwords = ["Second", "sentence", "to", "the", "right."]
                            ForEach(bwords, id: \.self) { bword in
                                Text(bword)
                            }
                    }
                }
            }
        }
    }
}

I’m not sure if I’m doing this right (the code I pasted). My problem is that the words with different URL’s are cut vertically as you see in the screen-dump from the simulator. I guess this needs a more complicated layout, and I have to investigate more. Just wanted to clarify. The first to sentences has no URLs and are cut nicely, but the two senteces “with words from the array” and different URL’s are cut vertically on each word and not so nice. The URL is though working fine.

Mm, yeah, looks like I missed that requirement.

@mlp There’s unfortunately no easy way to do this in SwiftUI.

The best way I can think of would be to create a UIViewRepresentable that wraps a UITextView and then assign its text property from an NSAttributedString that contains the words linked to their URLs. I won’t have time for the next several hours but I could whip up a proof of concept later to demonstrate what I mean.

Here’s what I came up with. It’s pretty simplistic but gets the job done. Some notes:

  1. You have no control over the tapping; it opens the appropriate URL but if you want to do something in addition, you’re out of luck.
  2. This builds the list of words+URLs once and then it’s done but you could instead use a binding to a list of words+URLs and make the list dynamic.
  3. I used a tuple of (String, URL) for the word list but you could use a Dictionary instead
  4. If you want this to support dynamic type, you would have to add a binding to a UIFont.TextStyle, like .body or .title or whatever and then apply it on the wrapped UITextView

Anyway, there’s probably a lot of ways this could be jazzed up a bit, but it shows how something could be achieved with some effort. Hope it helps or sparks some further ideas.

import SwiftUI
import UIKit

struct WordListTextView: UIViewRepresentable {
    var wordList = NSMutableAttributedString()
    
    init(wordsAndURLs: [(String, URL)]) {
        let separator = NSAttributedString(string: " ")
        let list = wordsAndURLs
            .map { NSAttributedString(string: $0.0, attributes: [NSAttributedString.Key.link : $0.1]) }
            .reduce(into: NSMutableAttributedString()) { (acc, next) in
                if acc.length > 0 {
                    acc.append(separator)
                }
                acc.append(next)
            }
        self.wordList = list
    }
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.isEditable = false
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.attributedText = wordList
    }
}

struct WordListView: View {
    private let wordsAndURLs: [(String, URL)] = [
        ("google", URL(string: "https://www.google.com")!),
        ("stack overflow", URL(string: "https://www.stackoverflow.com")!),
        ("code with chris", URL(string: "https://codewithchris.com")!)
    ]
    
    var body: some View {
        HStack {
            WordListTextView(wordsAndURLs: wordsAndURLs)
                .frame(width: 200)
                .border(Color.green, width: 1)
            Spacer()
            WordListTextView(wordsAndURLs: wordsAndURLs.shuffled())
                .frame(width: 200)
                .border(Color.green, width: 1)
        }
    }
}


struct WordListView_Previews: PreviewProvider {
    static var previews: some View {
        WordListView()
    }
}

I found this today, and will have a look at it. swift - SwiftUI HStack with wrap and dynamic height - Stack Overflow

And I will have a look at your suggestion. It wasn’t as easy as I thought to solve with SwiftUI.
Kind regards :slight_smile: