最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - How can I add a pinch (magnification & rotation) gesture for resizing in my SwiftUI view along with the existing d

programmeradmin1浏览0评论

I'm working on a SwiftUI view that displays some text which can be moved, resized, and rotated. Right now, I have a working solution where the user can drag a resize arrow (located at the bottom-right) to change the size (and rotation) of the text.

Here's a simplified version of my code:

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct ResizableTextView: View {
    let text: String
    let isEdittable: Bool
    
    /// Called when the edit (pen) button is tapped.
    var onEdit: (() -> Void)?
    
    /// Called when the close (delete) button is tapped.
    var onDelete: (() -> Void)?
    
    var onTap: (() -> Void)?
    
    // MARK: - Transformation State Variables
    @State private var scale: CGFloat = 1.0
    @State private var rotation: Angle = .zero

    // "Committed" transformation values from previous gestures.
    @State private var lastScale: CGFloat = 1.0
    @State private var lastRotation: Angle = .zero

    // For tracking the resize gesture’s starting values.
    @State private var initialDistance: CGFloat = 0
    @State private var initialAngle: Angle = .zero
    @State private var isDragging: Bool = false

    // The intrinsic size of the text (including padding), measured before any transforms.
    @State private var textSize: CGSize = .zero

    // MARK: - Position (Drag-to-move) State Variables
    @State private var positionOffset: CGSize = .zero
    @State private var lastPositionOffset: CGSize = .zero

    var body: some View {
        GeometryReader { geometry in
            // The container's center is used as the anchor point for the text.
            let containerCenter = CGPoint(x: geometry.size.width / 2,
                                          y: geometry.size.height / 2)
            
            ZStack {
                // Main text view with measured size.
                Text(text)
                    .padding(20)
                    .background(
                        GeometryReader { proxy in
                            Color.clear
                                .preference(key: SizePreferenceKey.self, value: proxy.size)
                        }
                    )
                    .onPreferenceChange(SizePreferenceKey.self) { newSize in
                        self.textSize = newSize
                    }
                    // Apply the current transformations.
                    .scaleEffect(scale)
                    .rotationEffect(rotation)
                    // Draw a border around the text.
                    .overlay(
                        Rectangle()
                            .stroke(Color.gray, lineWidth: isEdittable ? 1 : 0)
                            .scaleEffect(scale)
                            .rotationEffect(rotation)
                    )
                    // Position the text at the container center.
                    .position(containerCenter)
                
                // Calculate half of the scaled text dimensions.
                let halfWidth = (textSize.width * scale) / 2
                let halfHeight = (textSize.height * scale) / 2
                let angleRad = rotation.radians
                
                // MARK: - Compute Control Positions
                
                // Bottom-right (Resize control):
                let arrowOffsetX = halfWidth * CGFloat(cos(angleRad)) - halfHeight * CGFloat(sin(angleRad))
                let arrowOffsetY = halfWidth * CGFloat(sin(angleRad)) + halfHeight * CGFloat(cos(angleRad))
                let arrowPosition = CGPoint(x: containerCenter.x + arrowOffsetX,
                                            y: containerCenter.y + arrowOffsetY)
                
                // Top-right (Edit/Pen control):
                let penOffsetX = halfWidth * CGFloat(cos(angleRad)) + halfHeight * CGFloat(sin(angleRad))
                let penOffsetY = halfWidth * CGFloat(sin(angleRad)) - halfHeight * CGFloat(cos(angleRad))
                let penPosition = CGPoint(x: containerCenter.x + penOffsetX,
                                          y: containerCenter.y + penOffsetY)
                
                // Top-left (Close/Delete control):
                let closeOffsetX = -halfWidth * CGFloat(cos(angleRad)) + halfHeight * CGFloat(sin(angleRad))
                let closeOffsetY = -halfWidth * CGFloat(sin(angleRad)) - halfHeight * CGFloat(cos(angleRad))
                let closePosition = CGPoint(x: containerCenter.x + closeOffsetX,
                                            y: containerCenter.y + closeOffsetY)
                
                // MARK: - Add Controls Using Helper Functions
                if isEdittable {
                    closeButton(at: closePosition)
                    editButton(at: penPosition)
                    resizeButton(at: arrowPosition, containerCenter: containerCenter)
                }
            }
            // Apply the offset to the entire structure.
            .offset(x: positionOffset.width, y: positionOffset.height)
            // Attach a drag gesture to reposition the whole structure.
            .gesture(
                DragGesture()
                    .onChanged { value in
                        positionOffset = CGSize(width: lastPositionOffset.width + value.translation.width,
                                                height: lastPositionOffset.height + value.translation.height)
                    }
                    .onEnded { _ in
                        lastPositionOffset = positionOffset
                    }
            )
            .onTapGesture {
                onTap?()
            }
            // Ensure the ZStack fills the container.
            .frame(width: geometry.size.width, height: geometry.size.height)
        }
        // Set an arbitrary height for the view (adjust as needed).
        .frame(height: 300)
    }
    
    // MARK: - Helper Functions for Control Buttons
    
    private func closeButton(at position: CGPoint) -> some View {
        Image("iconClose")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(.zero)
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .onTapGesture {
                onDelete?()
            }
    }
    
    private func editButton(at position: CGPoint) -> some View {
        Image("iconPen")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(.zero)
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .onTapGesture {
                onEdit?()
            }
    }
    
    private func resizeButton(at position: CGPoint, containerCenter: CGPoint) -> some View {
        Image("iconResize")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(Angle(degrees: 80))
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        let currentLocation = value.location
                        // Compute the vector from the container's center to the current drag location.
                        let dx = currentLocation.x - containerCenter.x
                        let dy = currentLocation.y - containerCenter.y
                        let currentDistance = sqrt(dx * dx + dy * dy)
                        let currentAngle = Angle(radians: atan2(dy, dx))
                        
                        if !isDragging {
                            isDragging = true
                            initialDistance = currentDistance
                            initialAngle = currentAngle
                        } else {
                            // Adjust scale based on the change in distance.
                            let scaleFactor = currentDistance / initialDistance
                            var newScale = lastScale * scaleFactor
                            newScale = min(max(newScale, 0.1), 5.0)
                            scale = newScale
                            
                            // Adjust rotation based on the change in angle.
                            let angleDelta = currentAngle - initialAngle
                            rotation = lastRotation + angleDelta
                        }
                    }
                    .onEnded { _ in
                        isDragging = false
                        lastScale = scale
                        lastRotation = rotation
                    }
            )
    }
}

The drag-to-resize functionality via the arrow control works perfectly. However, I also want the user to be able to resize (and possibly rotate) the view using a two-finger pinch gesture directly on the text (or on the entire view), rather than having to drag the resize arrow.

I've tried looking into adding a MagnificationGesture (and perhaps a RotationGesture) to handle the pinch, but I'm running into a couple of issues:

  • Gesture conflicts: How do I combine or distinguish between the existing drag gestures (for moving and resizing) and the new pinch gestures?

  • Updating transformations: How can I update the scale (and potentially rotation) state variables based on the pinch gesture in a way that integrates well with the current drag-to-resize implementation?

Does anyone have suggestions or examples on how to integrate a pinch gesture (using MagnificationGesture and possibly RotationGesture) into this view to allow pinch-to-resize (and/or rotate) functionality?

I'm working on a SwiftUI view that displays some text which can be moved, resized, and rotated. Right now, I have a working solution where the user can drag a resize arrow (located at the bottom-right) to change the size (and rotation) of the text.

Here's a simplified version of my code:

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct ResizableTextView: View {
    let text: String
    let isEdittable: Bool
    
    /// Called when the edit (pen) button is tapped.
    var onEdit: (() -> Void)?
    
    /// Called when the close (delete) button is tapped.
    var onDelete: (() -> Void)?
    
    var onTap: (() -> Void)?
    
    // MARK: - Transformation State Variables
    @State private var scale: CGFloat = 1.0
    @State private var rotation: Angle = .zero

    // "Committed" transformation values from previous gestures.
    @State private var lastScale: CGFloat = 1.0
    @State private var lastRotation: Angle = .zero

    // For tracking the resize gesture’s starting values.
    @State private var initialDistance: CGFloat = 0
    @State private var initialAngle: Angle = .zero
    @State private var isDragging: Bool = false

    // The intrinsic size of the text (including padding), measured before any transforms.
    @State private var textSize: CGSize = .zero

    // MARK: - Position (Drag-to-move) State Variables
    @State private var positionOffset: CGSize = .zero
    @State private var lastPositionOffset: CGSize = .zero

    var body: some View {
        GeometryReader { geometry in
            // The container's center is used as the anchor point for the text.
            let containerCenter = CGPoint(x: geometry.size.width / 2,
                                          y: geometry.size.height / 2)
            
            ZStack {
                // Main text view with measured size.
                Text(text)
                    .padding(20)
                    .background(
                        GeometryReader { proxy in
                            Color.clear
                                .preference(key: SizePreferenceKey.self, value: proxy.size)
                        }
                    )
                    .onPreferenceChange(SizePreferenceKey.self) { newSize in
                        self.textSize = newSize
                    }
                    // Apply the current transformations.
                    .scaleEffect(scale)
                    .rotationEffect(rotation)
                    // Draw a border around the text.
                    .overlay(
                        Rectangle()
                            .stroke(Color.gray, lineWidth: isEdittable ? 1 : 0)
                            .scaleEffect(scale)
                            .rotationEffect(rotation)
                    )
                    // Position the text at the container center.
                    .position(containerCenter)
                
                // Calculate half of the scaled text dimensions.
                let halfWidth = (textSize.width * scale) / 2
                let halfHeight = (textSize.height * scale) / 2
                let angleRad = rotation.radians
                
                // MARK: - Compute Control Positions
                
                // Bottom-right (Resize control):
                let arrowOffsetX = halfWidth * CGFloat(cos(angleRad)) - halfHeight * CGFloat(sin(angleRad))
                let arrowOffsetY = halfWidth * CGFloat(sin(angleRad)) + halfHeight * CGFloat(cos(angleRad))
                let arrowPosition = CGPoint(x: containerCenter.x + arrowOffsetX,
                                            y: containerCenter.y + arrowOffsetY)
                
                // Top-right (Edit/Pen control):
                let penOffsetX = halfWidth * CGFloat(cos(angleRad)) + halfHeight * CGFloat(sin(angleRad))
                let penOffsetY = halfWidth * CGFloat(sin(angleRad)) - halfHeight * CGFloat(cos(angleRad))
                let penPosition = CGPoint(x: containerCenter.x + penOffsetX,
                                          y: containerCenter.y + penOffsetY)
                
                // Top-left (Close/Delete control):
                let closeOffsetX = -halfWidth * CGFloat(cos(angleRad)) + halfHeight * CGFloat(sin(angleRad))
                let closeOffsetY = -halfWidth * CGFloat(sin(angleRad)) - halfHeight * CGFloat(cos(angleRad))
                let closePosition = CGPoint(x: containerCenter.x + closeOffsetX,
                                            y: containerCenter.y + closeOffsetY)
                
                // MARK: - Add Controls Using Helper Functions
                if isEdittable {
                    closeButton(at: closePosition)
                    editButton(at: penPosition)
                    resizeButton(at: arrowPosition, containerCenter: containerCenter)
                }
            }
            // Apply the offset to the entire structure.
            .offset(x: positionOffset.width, y: positionOffset.height)
            // Attach a drag gesture to reposition the whole structure.
            .gesture(
                DragGesture()
                    .onChanged { value in
                        positionOffset = CGSize(width: lastPositionOffset.width + value.translation.width,
                                                height: lastPositionOffset.height + value.translation.height)
                    }
                    .onEnded { _ in
                        lastPositionOffset = positionOffset
                    }
            )
            .onTapGesture {
                onTap?()
            }
            // Ensure the ZStack fills the container.
            .frame(width: geometry.size.width, height: geometry.size.height)
        }
        // Set an arbitrary height for the view (adjust as needed).
        .frame(height: 300)
    }
    
    // MARK: - Helper Functions for Control Buttons
    
    private func closeButton(at position: CGPoint) -> some View {
        Image("iconClose")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(.zero)
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .onTapGesture {
                onDelete?()
            }
    }
    
    private func editButton(at position: CGPoint) -> some View {
        Image("iconPen")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(.zero)
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .onTapGesture {
                onEdit?()
            }
    }
    
    private func resizeButton(at position: CGPoint, containerCenter: CGPoint) -> some View {
        Image("iconResize")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(Angle(degrees: 80))
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        let currentLocation = value.location
                        // Compute the vector from the container's center to the current drag location.
                        let dx = currentLocation.x - containerCenter.x
                        let dy = currentLocation.y - containerCenter.y
                        let currentDistance = sqrt(dx * dx + dy * dy)
                        let currentAngle = Angle(radians: atan2(dy, dx))
                        
                        if !isDragging {
                            isDragging = true
                            initialDistance = currentDistance
                            initialAngle = currentAngle
                        } else {
                            // Adjust scale based on the change in distance.
                            let scaleFactor = currentDistance / initialDistance
                            var newScale = lastScale * scaleFactor
                            newScale = min(max(newScale, 0.1), 5.0)
                            scale = newScale
                            
                            // Adjust rotation based on the change in angle.
                            let angleDelta = currentAngle - initialAngle
                            rotation = lastRotation + angleDelta
                        }
                    }
                    .onEnded { _ in
                        isDragging = false
                        lastScale = scale
                        lastRotation = rotation
                    }
            )
    }
}

The drag-to-resize functionality via the arrow control works perfectly. However, I also want the user to be able to resize (and possibly rotate) the view using a two-finger pinch gesture directly on the text (or on the entire view), rather than having to drag the resize arrow.

I've tried looking into adding a MagnificationGesture (and perhaps a RotationGesture) to handle the pinch, but I'm running into a couple of issues:

  • Gesture conflicts: How do I combine or distinguish between the existing drag gestures (for moving and resizing) and the new pinch gestures?

  • Updating transformations: How can I update the scale (and potentially rotation) state variables based on the pinch gesture in a way that integrates well with the current drag-to-resize implementation?

Does anyone have suggestions or examples on how to integrate a pinch gesture (using MagnificationGesture and possibly RotationGesture) into this view to allow pinch-to-resize (and/or rotate) functionality?

Share Improve this question asked 2 days ago Codebane the SwiftbreakerCodebane the Swiftbreaker 331 silver badge5 bronze badges New contributor Codebane the Swiftbreaker is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
Add a comment  | 

1 Answer 1

Reset to default 1

You can use .simultaneousGesture to add a MagnifyGesture and just use your existing scale state to update on changes.

Add these modifiers to your Text view:

.simultaneousGesture(
    MagnifyGesture()
        .onChanged { value in
            scale = value.magnification
        }
        .onEnded { _ in
            withAnimation {
                scale = max(1.0, scale) // Prevent shrinking too much
            }
        }
)

Same for the RotationGesture, use your rotation state:

.gesture(
    RotationGesture()
        .onChanged { value in
            rotation = value
        }
)

But, it's better practice to define the gestures separately (rather than within the .gesture modifier). You can define them in the body, before the ZStack, like this:

//Drag gesture
let dragGesture =  DragGesture()
    .onChanged { value in
        positionOffset = CGSize(width: lastPositionOffset.width + value.translation.width,
                                height: lastPositionOffset.height + value.translation.height)
    }
    .onEnded { _ in
        lastPositionOffset = positionOffset
    }

//Magnify gesture
let magnifyGesture = MagnifyGesture()
    .onChanged { value in
        scale = value.magnification
    }
    .onEnded { _ in
        withAnimation {
            scale = max(1.0, scale) // Prevent shrinking too much
        }
    }

//Rotation gesture
let rotationGesture = RotationGesture()
    .onChanged { value in
        rotation = value
    }

Then, your existing gesture can simply be applied like this: .gesture(dragGesture)

And the new modifiers on the Text, like this:

.simultaneousGesture(magnifyGesture)
.gesture(rotationGesture)

But the real benefit lies in being able to combine gesture more easily, so instead of having three modifiers, you can do it all in your existing .gesture modifier:

.gesture(
    dragGesture
        .simultaneously(with: magnifyGesture)
        .simultaneously(with: rotationGesture)
)

Here's your complete updated code:

import SwiftUI

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct ResizableTextView: View {
    let text: String
    let isEdittable: Bool
    
    /// Called when the edit (pen) button is tapped.
    var onEdit: (() -> Void)?
    
    /// Called when the close (delete) button is tapped.
    var onDelete: (() -> Void)?
    
    var onTap: (() -> Void)?
    
    // MARK: - Transformation State Variables
    @State private var scale: CGFloat = 1.0
    @State private var rotation: Angle = .zero

    // "Committed" transformation values from previous gestures.
    @State private var lastScale: CGFloat = 1.0
    @State private var lastRotation: Angle = .zero

    // For tracking the resize gesture’s starting values.
    @State private var initialDistance: CGFloat = 0
    @State private var initialAngle: Angle = .zero
    @State private var isDragging: Bool = false

    // The intrinsic size of the text (including padding), measured before any transforms.
    @State private var textSize: CGSize = .zero

    // MARK: - Position (Drag-to-move) State Variables
    @State private var positionOffset: CGSize = .zero
    @State private var lastPositionOffset: CGSize = .zero

    
    var body: some View {
        GeometryReader { geometry in
            // The container's center is used as the anchor point for the text.
            let containerCenter = CGPoint(x: geometry.size.width / 2,
                                          y: geometry.size.height / 2)
            
            //Drag gesture
            let dragGesture =  DragGesture()
                .onChanged { value in
                    positionOffset = CGSize(width: lastPositionOffset.width + value.translation.width,
                                            height: lastPositionOffset.height + value.translation.height)
                }
                .onEnded { _ in
                    lastPositionOffset = positionOffset
                }
            
            //Magnify gesture
            let magnifyGesture = MagnifyGesture()
                .onChanged { value in
                    scale = value.magnification
                }
                .onEnded { _ in
                    withAnimation {
                        scale = max(1.0, scale) // Prevent shrinking too much
                    }
                }
            
            //Rotation gesture
            let rotationGesture = RotationGesture()
                .onChanged { value in
                    rotation = value
                }
            
            ZStack {
                // Main text view with measured size.
                Text(text)
                    .padding(20)
                    .background(
                        GeometryReader { proxy in
                            Color.clear
                                .preference(key: SizePreferenceKey.self, value: proxy.size)
                        }
                    )
                    .onPreferenceChange(SizePreferenceKey.self) { newSize in
                        self.textSize = newSize
                    }
                    // Apply the current transformations.
                    .scaleEffect(scale)
                    .rotationEffect(rotation)
                    // Draw a border around the text.
                    .overlay(
                        Rectangle()
                            .stroke(Color.gray, lineWidth: isEdittable ? 1 : 0)
                            .scaleEffect(scale)
                            .rotationEffect(rotation)
                    )
                    // Position the text at the container center.
                    .position(containerCenter)
                
                // Calculate half of the scaled text dimensions.
                let halfWidth = (textSize.width * scale) / 2
                let halfHeight = (textSize.height * scale) / 2
                let angleRad = rotation.radians
                
                // MARK: - Compute Control Positions
                
                // Bottom-right (Resize control):
                let arrowOffsetX = halfWidth * CGFloat(cos(angleRad)) - halfHeight * CGFloat(sin(angleRad))
                let arrowOffsetY = halfWidth * CGFloat(sin(angleRad)) + halfHeight * CGFloat(cos(angleRad))
                let arrowPosition = CGPoint(x: containerCenter.x + arrowOffsetX,
                                            y: containerCenter.y + arrowOffsetY)
                
                // Top-right (Edit/Pen control):
                let penOffsetX = halfWidth * CGFloat(cos(angleRad)) + halfHeight * CGFloat(sin(angleRad))
                let penOffsetY = halfWidth * CGFloat(sin(angleRad)) - halfHeight * CGFloat(cos(angleRad))
                let penPosition = CGPoint(x: containerCenter.x + penOffsetX,
                                          y: containerCenter.y + penOffsetY)
                
                // Top-left (Close/Delete control):
                let closeOffsetX = -halfWidth * CGFloat(cos(angleRad)) + halfHeight * CGFloat(sin(angleRad))
                let closeOffsetY = -halfWidth * CGFloat(sin(angleRad)) - halfHeight * CGFloat(cos(angleRad))
                let closePosition = CGPoint(x: containerCenter.x + closeOffsetX,
                                            y: containerCenter.y + closeOffsetY)
                
                // MARK: - Add Controls Using Helper Functions
                if isEdittable {
                    closeButton(at: closePosition)
                    editButton(at: penPosition)
                    resizeButton(at: arrowPosition, containerCenter: containerCenter)
                }
            }
            // Apply the offset to the entire structure.
            .offset(x: positionOffset.width, y: positionOffset.height)
            // Attach a drag gesture to reposition the whole structure.
            .gesture(
               dragGesture
                .simultaneously(with: magnifyGesture)
                .simultaneously(with: rotationGesture)
            )
            .onTapGesture {
                onTap?()
            }
            // Ensure the ZStack fills the container.
            .frame(width: geometry.size.width, height: geometry.size.height)
        }
        // Set an arbitrary height for the view (adjust as needed).
        .frame(height: 300)
    }
    
    // MARK: - Helper Functions for Control Buttons
    
    private func closeButton(at position: CGPoint) -> some View {
        Image("iconClose")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(.zero)
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .onTapGesture {
                onDelete?()
            }
    }
    
    private func editButton(at position: CGPoint) -> some View {
        Image("iconPen")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(.zero)
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .onTapGesture {
                onEdit?()
            }
    }
    
    private func resizeButton(at position: CGPoint, containerCenter: CGPoint) -> some View {
        Image("iconResize")
            .resizable()
            .scaledToFit()
            .frame(width: 16, height: 16)
            .foregroundStyle(.black)
            .rotationEffect(Angle(degrees: 80))
            .padding(4)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: 28, height: 28)
                    .shadow(radius: 5)
            )
            .position(position)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        let currentLocation = value.location
                        // Compute the vector from the container's center to the current drag location.
                        let dx = currentLocation.x - containerCenter.x
                        let dy = currentLocation.y - containerCenter.y
                        let currentDistance = sqrt(dx * dx + dy * dy)
                        let currentAngle = Angle(radians: atan2(dy, dx))
                        
                        if !isDragging {
                            isDragging = true
                            initialDistance = currentDistance
                            initialAngle = currentAngle
                        } else {
                            // Adjust scale based on the change in distance.
                            let scaleFactor = currentDistance / initialDistance
                            var newScale = lastScale * scaleFactor
                            newScale = min(max(newScale, 0.1), 5.0)
                            scale = newScale
                            
                            // Adjust rotation based on the change in angle.
                            let angleDelta = currentAngle - initialAngle
                            rotation = lastRotation + angleDelta
                        }
                    }
                    .onEnded { _ in
                        isDragging = false
                        lastScale = scale
                        lastRotation = rotation
                    }
            )
    }
}

#Preview {
    ResizableTextView(text: "Scale and rotate me", isEdittable: true)
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论