iosswiftuisf-symbols

How can I get 3 combined `SF Symbol` images to rotate around a specific point?


I have three (3) SF Symbols grouped and arranged in a specific relationship to each other. Now I want the entire group to rotate, as one item, around a specific point.

enter image description here

I have spent hours with trial & error (mostly error) trying to adjust combinations of padding and offsets and anchor points.

Context:

This rotatable view will be used as part of an iOS 17 Annotation using MapKit to mark a specific GPS location indicating the direction of travel (hence the rotating bit and the desired anchor point).

IF there is a better way to accomplish my ultimate goal … I'm open to all ideas. :-)

Here is my code:

import SwiftUI

struct StarArrowView: View {
   
   var bumpColor: Color
   @State var rotaAtAnchor = false
   var body: some View {
      ZStack {
         Divider()
         Divider().offset(y: 25)
         Divider().offset(y: -25)
         Divider().rotationEffect(.degrees(90))
         Divider().rotationEffect(.degrees(90)).offset(x: 25)
         Divider().rotationEffect(.degrees(90)).offset(x: -25)
         Group {
            HStack(spacing: 0) {
               Image(systemName: "star.fill")
                  .symbolRenderingMode(.palette)
                  .foregroundStyle(bumpColor)
                  .font(.system(size: 30, weight: .light))
                  .padding(0)
                  .offset(x: 8, y: 6)
               Image(systemName: "line.diagonal.arrow")
                  .foregroundStyle(bumpColor)
                  .font(.system(size: 40, weight: .light))
                  .padding(0)
                  .offset(x: -10, y: -15)
               Text("➘")
                  .symbolRenderingMode(.palette)
                  .foregroundStyle(bumpColor, bumpColor)
                  .font(.system(size: 30, weight: .ultraLight))
                  .rotationEffect(.degrees(190), anchor: .topLeading)
                  .padding(0)
                  .offset(x: 0, y: 0)
            }
            .padding(.vertical, -20)
            .padding(.horizontal, -5)
            .rotationEffect(.degrees(45))
            .offset(x: 15, y: 22)
            
            
            .rotationEffect(.degrees(rotaAtAnchor ? 0 : 180), anchor: .trailing) //Anchor Position
            .animation(Animation.spring ().repeatForever(autoreverses: true))
            .onAppear() {
               self.rotaAtAnchor.toggle()
            }
         }
      }
   }
}
   


#Preview {
   StarArrowView(bumpColor: .heatmap10)
}


Solution

  • The tricky part about this problem is getting the anchor point right. One way would be to use a UnitPoint with fractional position. Alternatively, in order to use one of the edges of the group as the anchor point, the arrow pointing up needs to be moved out of the bounds of the group. This can be done using an overlay.

    Here is an attempt to get it working. Some notes:

    ZStack {
        Divider()
        Divider().offset(y: 25)
        Divider().offset(y: -25)
        Divider().rotationEffect(.degrees(90))
        Divider().rotationEffect(.degrees(90)).offset(x: 25)
        Divider().rotationEffect(.degrees(90)).offset(x: -25)
    
        ZStack(alignment: .leading) {
            Image(systemName: "star.fill")
                .symbolRenderingMode(.palette)
                .foregroundStyle(bumpColor)
                .font(.system(size: 30, weight: .light))
                .rotationEffect(.degrees(45))
            Image(systemName: "line.diagonal.arrow")
                .foregroundStyle(bumpColor)
                .font(.system(size: 40, weight: .light))
                .rotationEffect(.degrees(45))
                .padding(.leading, 30)
        }
        .padding(.trailing, 10)
        .overlay(alignment: .topTrailing) {
            Text("➘")
                .symbolRenderingMode(.palette)
                .foregroundStyle(bumpColor, bumpColor)
                .font(.system(size: 30, weight: .ultraLight))
                .rotationEffect(.degrees(235))
                .frame(width: 30)
                .offset(x: 15)
        }
        .rotationEffect(.degrees(rotaAtAnchor ? 180 : 0), anchor: .trailing)
    
        // Fine adjustment of the position of the entire group
        .padding(.leading, 30)
        .padding(.top, 20)
    
        .animation(Animation.spring ().repeatForever(autoreverses: true), value: rotaAtAnchor)
        .onAppear() {
           self.rotaAtAnchor.toggle()
        }
    }
    

    Animation