1

I want to use string interpolation on an SF Symbol that has a rotationEffect(_:anchor:) modifier applied to it. Is it possible to do this?

Without the modifier this type of string interpolation works fine (in Swift 5.0):

struct ContentView: View {
    var body: some View {
        Text("Some text before \(Image(systemName: "waveform.circle")) plus some text after.")
    }
}

But applying the modifier like this:

struct ContentView: View {
    var body: some View {
        Text("Some text before \(Image(systemName: "waveform.circle").rotationEffect(.radians(.pi * 0.5))) plus some text after.")
    }
}

doesn't compile and gives this error:

Instance method 'appendInterpolation' requires that 'some View' conform to '_FormatSpecifiable'

TylerP
  • 9,600
  • 4
  • 39
  • 43
tim
  • 57
  • 7

2 Answers2

1

The Text interpolation expect an Image. When the .rotationEffect... is applied it becomes a View, and this is not valid.

So an alternative is to rotate the SF before it is used in Image. This is what I ended up trying, using the code from one of the answers at: Rotating UIImage in Swift to rotate a UIImage and using that in the Image.

It is a bit convoluted, and you will probably have to adjust the anchor/position. Perhaps someone will come up with a better solution. Until then it seems to works for me.

struct ContentView: View {
    var body: some View {
        Text("Some text before \(img) plus some text after.")
    }
    
    var img: Image {
        if let uimg = UIImage(systemName: "waveform.circle"),
           let rotImage = uimg.rotate(radians: .pi/4) {
            return Image(uiImage: rotImage)
        } else {
            return Image(systemName: "waveform.circle")
        }
    }
     
}

// from: https://stackoverflow.com/questions/27092354/rotating-uiimage-in-swift
extension UIImage {
    func rotate(radians: Float) -> UIImage? {
        var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
        // Trim off the extremely small float value to prevent core graphics from rounding it up
        newSize.width = floor(newSize.width)
        newSize.height = floor(newSize.height)

        UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
        let context = UIGraphicsGetCurrentContext()!

        // Move origin to middle
        context.translateBy(x: newSize.width/2, y: newSize.height/2)
        // Rotate around middle
        context.rotate(by: CGFloat(radians))
        // Draw the image at its center
        self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage
    }
}
  • Thanks for your help, this was useful and I will upvote it when I can (not enough reputation yet). The alignment of the symbol is a bit off and varies depending on the angle used. I came up with a solution aligns the symbol on the text baseline using the `orientation` value of the `UIImage`. I'll post it below. – tim Nov 02 '22 at 21:37
  • I guess the simple answer would be to just create a symbol with the correct orientation, which would also take care of the alignment issue. Was just hoping there was neat way to do it without – tim Nov 02 '22 at 22:11
  • Yes, if you only ever want a fixed orientation, then creating that one particular symbol with that orientation is a good way to do it. If my answer helped you, consider accepting it, with the tick mark, next to my answer, it turns green. You have enough rep for it. – workingdog support Ukraine Nov 02 '22 at 23:16
0

As pointed out here by workingdog_support_Ukraine, string interpolation will work with Image but modifiers will change the type they are applied to. So we need to rotate the image without erasing the Image type.

For simple orientation rotations we can create our rotated Image type like this:

extension Image {
    init(systemName: String, orientation: UIImage.Orientation) {
        guard
            let uiImage = UIImage(systemName: systemName),
            let cgImage = uiImage.cgImage
        else {
            self.init(systemName: systemName)
            return
        }

        self.init(uiImage: UIImage(cgImage: cgImage, scale: uiImage.scale, orientation: orientation))
    }
}

We then use a rotated image (in this case, rotated 90 degrees clockwise) in our string interpolation, as follows:

struct ContentView: View {
    var body: some View {
        Text("Some text before \(Image(systemName: "waveform.circle", orientation: .right)) plus some text after.")
    }
}

This aligns the rotated image on the text baseline.

tim
  • 57
  • 7