Wrap text around some view in SwiftUI

Viewed 1186

I give up. I want to make a text wrap around some view and I can't find any way to do it.

enter image description here

Example Code:

struct ListView: View {
    // actual text from @ObservedObject 
    var exText = "The statue of Laocoön and His Sons, also called the Laocoön Group (Italian: Gruppo del Laocoonte), has been one of the most famous ancient sculptures ever since it was excavated in Rome in 1506 and placed on public display in the Vatican,[2] where it remains. It is very likely the same statue praised in the highest terms by the main Roman writer on art, Pliny the Elder.[3] The figures are near life-size and the group is a little over 2 m (6 ft 7 in) in height, showing the Trojan priest Laocoön and his sons Antiphantes and Thymbraeus being attacked by sea serpents.[1]"
    
    @State var thumbnail:UIImage = UIImage()
    
    var body: some View {
        HStack {
            Image(uiImage: thumbnail)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 70.0, height: 70.0)
                .background(Color.gray)
                //.onReceive() to update the image from the @ObservedObject 
            Text(exText)
                .lineLimit(nil)
        }
    }
}

If I use text(UIImage()) to insert the image, it shows the bottom of the image in line with the first line of the text. Plus, this method doesn't let me customise the UIImage view. All the methods .mask, .clipShape, .compositingGroup and .contentShape didn't work out at all since they just clip the text and don't let it wrap around the other view. With UIKit this was doable by using exclusionPaths, is there an equivalent to this in SwiftUI?

I googled and searched the documentation for hours and all I can find is this question here which didn't get answered either. Swift gods please answer my prayers!

2 Answers

You can use the "+" operator. Put the image inside: Text(Image(…)).

Text("here is the star:") + Text(Image(systemName:"star")) + Text(", very easy.")

You can try this sample in your playground: https://github.com/nferocious76/SwiftUI-Playground/tree/master/MyPlayground.playground/Pages/TextView%2BExclusionPath.xcplaygroundpage

import Foundation
import SwiftUI
import UIKit
import PlaygroundSupport

var greeting = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.\n\nLorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

var initialText: String {
  String(greeting[...greeting.startIndex])
}

var bodyText: String {
  String(greeting[greeting.index(after: greeting.startIndex)...])
}

struct TextView: UIViewRepresentable {
  typealias UIViewType = UITextView

  @Binding private var text: String

  private let font: UIFont?
  private let textColor: UIColor?
  private let textAlignment: NSTextAlignment
  private let exclusionPaths: [UIBezierPath]

  private let isEditable: Bool
  private let isSelectable: Bool
  private let autocorrectionType: UITextAutocorrectionType
  private let autocapitalizationType: UITextAutocapitalizationType

  init(text: Binding<String>,
       font: UIFont? = .systemFont(ofSize: 10),
       textColor: UIColor? = .black,
       textAlignment: NSTextAlignment = .left,
       exclusionPaths: [UIBezierPath],

       isEditable: Bool = false,
       isSelectable: Bool = false,
       autocorrectionType: UITextAutocorrectionType = .default,
       autocapitalizationType: UITextAutocapitalizationType = .sentences) {

    _text = text
    self.font = font
    self.textColor = textColor
    self.textAlignment = textAlignment
    self.exclusionPaths = exclusionPaths

    self.isEditable = isEditable
    self.isSelectable = isSelectable

    self.autocorrectionType = autocorrectionType
    self.autocapitalizationType = autocapitalizationType
  }

  init(text: String,
       font: UIFont? = .systemFont(ofSize: 10),
       textColor: UIColor? = .black,
       textAlignment: NSTextAlignment = .left,
       exclusionPaths: [UIBezierPath],

       isEditable: Bool = false,
       isSelectable: Bool = false,
       autocorrectionType: UITextAutocorrectionType = .default,
       autocapitalizationType: UITextAutocapitalizationType = .sentences) {

    _text = .constant(text)
    self.font = font
    self.textColor = textColor
    self.textAlignment = textAlignment
    self.exclusionPaths = exclusionPaths

    self.isEditable = isEditable
    self.isSelectable = isSelectable

    self.autocorrectionType = autocorrectionType
    self.autocapitalizationType = autocapitalizationType
  }

  func makeUIView(context: Context) -> UITextView {
    let textView = UITextView()
    textView.backgroundColor = .clear

    textView.text = text
    textView.font = font
    textView.textColor = textColor
    textView.textAlignment = textAlignment
    textView.isSelectable = isSelectable
    textView.isEditable = isEditable
    textView.textContainer.exclusionPaths = exclusionPaths

    textView.autocorrectionType = autocorrectionType
    textView.autocapitalizationType = autocapitalizationType

    return textView
  }

  func updateUIView(_ uiView: UITextView, context: Context) {
    uiView.text = text
  }
}

func createView() -> some View {
  VStack(spacing: 24) {
    let path = UIBezierPath(rect: CGRect(x: 50, y: 50, width: 50, height: 50))
    TextView(text: greeting,
             textAlignment: .justified,
             exclusionPaths: [path])
    .background(.gray)

    ZStack {
      let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
      TextView(text: bodyText,
               exclusionPaths: [path])
      .background(.orange)
      .overlay {
        GeometryReader { geometry in
          HStack {
            Text(initialText)
              .font(Font.system(size: 50, weight: .bold, design: .rounded))
              .foregroundColor(.green)
              .background(.blue)
              .frame(width: 50, height: 50, alignment: .center)
          }
          .frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
        }
      }
    }
    .frame(alignment: .topLeading)
  }
  .frame(width: 300, height: 600, alignment: .center)
  .background(.black)
}

PlaygroundPage.current.liveView = UIHostingController(rootView: createView())

I added the backgrounds so the canvas can easily be seen.

enter image description here

Related