CGAffineTransform scale resize not working properly when used in SwiftUI

Viewed 216

I tried to use the custom view button I created from UIKit to SwiftUI view using UIViewRepresentable but it did not work as expected.

Expected:

The view button will shrink upon tap or hold.

Expected_demo

Actual:

The view button expands when tap initially then shrink down when released

Actual_demo

The custom view button works properly in UIKit controllers.

Custom Button View Created in UIKit

public class CustomButton: UIView {

    fileprivate var clickedBtn: (() -> Void)?
.
.
.
.

    public func addAction(withEvent event: UIControl.Event = .touchUpInside, handler clickedHandler: @escaping () -> Void) {
        button.addTarget(self, action: #selector(holdRelease(_:)), for: .touchUpInside)
        button.addTarget(self, action: #selector(holdDown(_:)), for: .touchDown)
        button.addTarget(self, action: #selector(holdRelease(_:)), for: .touchDragExit)
        }
        clickedBtn = clickedHandler
    }
    
    @objc public func holdDown(_ sender: CXButton) {
        setupUIforHoldDown()
    }
    
    @objc public func holdRelease(_ sender: CXButton) {
        setupUIForHoldRelease()
        clickedBtn?()
    }
.
.
.
.
    
    /// Button rescales when touchDown. Button resizes back to 100% once release.
    private func setupUIforHoldDown() {
        rescaleButtonSize(scaleXto: 0.95, scaleYto: 0.95)
    }
    
    private func setupUIForHoldRelease() {
        rescaleButtonSize()
    }

    private func rescaleButtonSize(scaleXto x: CGFloat = 1.0,scaleYto y: CGFloat = 1.0) {
        DispatchQueue.main.async {
            UIView.animate(withDuration: 0.2, animations: {
                self.transform = CGAffineTransform(scaleX: x, y: y)
            })
        }
    }
}

UIViewRepresentable

import SwiftUI

struct SwiftUI_CustomButton: UIViewRepresentable {
    var title: String
    var handler: (() -> Void)? = nil
    
    func makeUIView(context: Context) -> CustomButton {
        let button = CustomButton()
        updateUIView(button, context: context)
        button.addAction {
            self.handler?()
        }
        return button
    }

    func updateUIView(_ button: CustomButton, context: Context) {
        button.title = title
    }
}

Swift UI View

struct CustomButton_SwiftUI: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            
            SwiftUI_CustomButton(
                title: "Actual"
            ) {
                // Button Action
            }
        }
        .padding(16)
    }
}

Edit: I'm still using UIKit custom view because it is also used on some UIKit controllers.

1 Answers

You can do it without UIKit, using a custom ButtonStyle:

struct CustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .foregroundColor(Color.accentColor)
            .scaleEffect(configuration.isPressed ? 0.95 : 1)
            .animation(.easeIn(duration: 0.2), value: configuration.isPressed)
    }
}

Usage:

Button(action: {}) {
    Text("Actual")
        .frame(height: 45)
        .frame(maxWidth: .infinity)
        .background(Color.black)
        .padding()
}.buttonStyle(CustomButtonStyle())

Result:

Related