Home > database >  Is there a way to use string interpolation with an SF Symbol that has a modifier on it?
Is there a way to use string interpolation with an SF Symbol that has a modifier on it?

Time:11-03

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'

CodePudding user response:

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

CodePudding user response:

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.

  • Related