I am new to SwiftUI. I have a project where the element called tile needs to have a dropdown menu button in right top corner. I have done that part and add the dropdown menu. But... Whenever I tap the button it swings to the side to be in the center with the content of the dropdown menu -> Screenrecording here
Also when the text displayed is shorter the dotted button disappears on tap, if I understand correctly it moves out of frame, righht? How can I prevent this?
Whhat am I missing, help will be much appreciated.
The code for that tile and menu below:
import SwiftUI
// just for testing purpose
let iconName = "ellipsis.rectangle"
protocol TileProtocol {
init (
icon: String,
text: String,
action: TileAction?,
hasMenuButton: Bool,
scaler: CGFloat
)
}
struct TileAction {
let title: String
let alignment: Alignment
let callback:(() -> Void)
}
struct TileWithMenu: TileProtocol, View {
let icon: String
let text: String
let action: TileAction?
let hasMenuButton: Bool
let scaler: CGFloat
var body: some View{
HStack(alignment: .top, spacing: 0) {
Image(icon)
.resizable()
.scaledToFit()
.frame(width: scaler, alignment: .leading)
.frame(maxWidth: 30, maxHeight: 30)
.padding(.top, 4)
.padding(.trailing, 10)
HStack(alignment: .center,spacing: 0) {
VStack(alignment: .leading,spacing: 16) {
Text(text)
.font(.headline)
if let action, action.alignment == .bottom {
VStack {
Button(action.title) {
action.callback()
}
}
}
}
}
}
.overlay(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
HStack(alignment: .lastTextBaseline, spacing: 10){
if hasMenuButton {
TileMenuButton(
options: [
TileAction(title: "Postpone for 24h", alignment: .trailing, callback: {
print("24h")
}),
TileAction(title: "Postpone for 24h", alignment: .trailing, callback: {
print("1 week")
}),
TileAction(title: "Cancel", alignment: .trailing, callback: {
print("cancel")
})
],
iconScaling: scaler
)
}
}
}
}
}
struct TileMenuButton: View {
@State private var isVisible: Bool = false
let options: [TileAction]
let iconScaling: CGFloat
var body: some View {
VStack {
Button(action: {
withAnimation {
isVisible.toggle()
}
}) {
Image(iconName)
.resizable()
.scaledToFit()
.frame(width: iconScaling)
}
.padding(2)
.background(.white)
if isVisible {
VStack {
ForEach(0..<options.count, id: \.self) { n in
Button(action: options[n].callback) {
Text(options[n].title)
.font(.caption)
.padding(.top, 1)
.background(.white)
}
}
}
.transition(.asymmetric(insertion: .scale, removal: .opacity))
}
}
}
}
// MARK: PREVIEW
struct TileWithMenu_Previews: PreviewProvider {
static var previews: some View {
TileWithMenu(
icon: iconName,
text: "Some example of a very long text. Some example of a very long text. Some example of a very long text",
action: TileAction(title: "A Button", alignment: .bottom, callback: {
print("A Button tap")
}),
hasMenuButton: true,
scaler: 20
)
}
}
And here is how I use it
struct ContentView: View {
var body: some View {
VStack {
TileWithMenu(
icon: iconName,
text: "Some example of a very long text. Some example of a very long text. Some example of a very long text",
action: TileAction(title: "A Button", alignment: .bottom, callback: {
print("A Button tap")
}),
hasMenuButton: true,
scaler: 20
)
}
.padding()
}
}
#Preview {
ContentView()
}
I was searching for the solution on line. Haven't found one so far.
To resolve the sideways movement, try adding (alignment: .trailing)
to the top-level VStack
of TileMenuButton
:
// TileMenuButton
VStack(alignment: .trailing) { // 👈 HERE
Button //...
if isVisible {
// ...
}
}
To resolve the issue of the menu icon disappearing when the text is short, try adding .fixedSize(horizontal: false, vertical: true)
to the HStack
inside the overlay of TileWithMenu
:
// TileWithMenu
HStack(alignment: .top, spacing: 0) {
// ...
}
.overlay(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
HStack(alignment: .lastTextBaseline, spacing: 10) {
// ...
}
.fixedSize(horizontal: false, vertical: true) // 👈 HERE
}