1

I'm trying to achieve this in UIKit but have had no luck so far 👇

VStack(spacing: 4) {
    Menu {
        Button("Me", systemImage: "location.north.fill") {
            
        }
        
        Button("Fin", systemImage: "flag.pattern.checkered.2.crossed") {
            
        }
    } label: {
        Label("Plus", systemImage: "plus")
            .padding()
    }
    
    Button {
        
    } label: {
        Label("QrCode", systemImage: "qrcode.viewfinder")
            .padding()
    }
}
.glassEffect(.regular.interactive())
.buttonStyle(.plain)
.labelStyle(.iconOnly)

The important thing is that the menu opening/closing animation needs to be a container morph animation just like in SwiftUI.

1
  • I'm quite surprised that I can't find an existing question simply about "how to add glass effect in UIKit", which would have been a duplicate target for this question, because other than that, there is nothing tricky about this. The SwiftUI code can be directly translated and the morphing animation all works the same way. Commented Nov 1 at 13:33

1 Answer 1

1

In this case a simple "direct translation" of the SwiftUI code to UIKit suffices.

To apply a glass effect to a UIView, put that view as the subview of the contentView of a UIVisualEffectView. The UIVisualEffectView should be initialised with a UIGlassEffect. See also the WWDC video where Apple talked about this.

That is,

let stackView = UIStackView(arrangedSubviews: [plusButton, qrCodeButton])
stackView.axis = .vertical
stackView.spacing = 4
let glassEffect = UIGlassEffect(style: .regular)
glassEffect.isInteractive = true
let glassEffectView = UIVisualEffectView(effect: glassEffect)
glassEffectView.cornerConfiguration = .capsule()
glassEffectView.contentView.addSubview(stackView)

Here is a more complete code showing the whole UIViewController subclass and the AutoLayout constraints,

class VC: UIViewController {
    override func loadView() {
        view = UIView()
        
        var plusButtonConfig = UIButton.Configuration.plain()
        plusButtonConfig.image = UIImage(systemName: "plus")
        let plusButton = UIButton(configuration: plusButtonConfig)
        plusButton.showsMenuAsPrimaryAction = true
        let meAction = UIAction(title: "Me", image: UIImage(systemName: "location.north.fill")) { _ in }
        let finAction = UIAction(title: "Fin", image: UIImage(systemName: "flag.pattern.checkered.2.crossed")) { _ in }
        plusButton.menu = UIMenu(children: [meAction, finAction])
        
        var qrCodeButtonConfig = UIButton.Configuration.plain()
        qrCodeButtonConfig.image = UIImage(systemName: "qrcode.viewfinder")
        let qrCodeButton = UIButton(configuration: qrCodeButtonConfig, primaryAction: UIAction { _ in })
        
        let stackView = UIStackView(arrangedSubviews: [plusButton, qrCodeButton])
        stackView.axis = .vertical
        stackView.spacing = 4
        let glassEffect = UIGlassEffect(style: .regular)
        glassEffect.isInteractive = true
        let glassEffectView = UIVisualEffectView(effect: glassEffect)
        glassEffectView.cornerConfiguration = .capsule()
        glassEffectView.contentView.addSubview(stackView)
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(glassEffectView)
        glassEffectView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            glassEffectView.topAnchor.constraint(equalTo: stackView.topAnchor, constant: -8),
            glassEffectView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 8),
            glassEffectView.leftAnchor.constraint(equalTo: stackView.leftAnchor),
            glassEffectView.rightAnchor.constraint(equalTo: stackView.rightAnchor),
            glassEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            glassEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

This approach works great — it was really eye-opening!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.