Hello,
I'm trying to create sort of a menu view where buttons are paid out in a grid. Each menu button consists of an icon and a title. And I have multiple menus like this throughout the app.
So I decided to create a reusable button view like this.
To keep the Menus organised, I opted to use an enum.
And I display he menu grid like so.
So far so good. The menu displays properly. But this is where I've hit a snag. I can't figure out a way to find which button user taps on.
I could include the menu type inside the MenuButton view itself and pass it back in the button tap closure.
But this makes the MenuButton view couple of one menu type and not reusable.
So I was wondering if there's another, better way to handle this. Any suggestions, ideas would be appreciated.
Thanks.
I'm trying to create sort of a menu view where buttons are paid out in a grid. Each menu button consists of an icon and a title. And I have multiple menus like this throughout the app.
So I decided to create a reusable button view like this.
Code Block swift struct MenuButton: View { let title: String let icon: Image var action: () -> Void var body: some View { Button(action: { action() }) { VStack { icon .resizable() .aspectRatio(contentMode: .fit) .frame(minWidth: 40, idealWidth: 50, maxWidth: 60, minHeight: 40, idealHeight: 50, maxHeight: 60) .padding(.bottom, 3) Text(title) .foregroundColor(.black) .font(.system(size: 15, weight: .bold)) .multilineTextAlignment(.center) .minimumScaleFactor(0.7) .lineLimit(2) } .padding(10) } .frame(maxWidth: .infinity, maxHeight: .infinity) .aspectRatio(1, contentMode: .fill) .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.blue, lineWidth: 0.6)) } }
To keep the Menus organised, I opted to use an enum.
Code Block swift enum Menu { enum UserType: CaseIterable, CustomStringConvertible { case new case existing var description: String { switch self { case .new: return "New User" case .existing: return "Existing User" } } var icon: String { switch self { case .new: return "new_user" case .existing: return "existing_user" } } } enum Main: CaseIterable, CustomStringConvertible { case create case search case notifications var description: String { switch self { case .create: return "Create" case .search: return "Search" case .notifications: return "Notifications" } } var icon: String { switch self { case .create: return "create" case .search: return "search" case .notifications: return "notifications" } } } }
And I display he menu grid like so.
Code Block swift struct ContentView: View { private let columns = [ GridItem(.flexible(), spacing: 20), GridItem(.flexible(), spacing: 20) ] var body: some View { ScrollView { LazyVGrid(columns: columns, spacing: 20) { ForEach(Menu.UserType.allCases, id: \.self) { item in MenuButton(title: item.description, icon: Image(item.icon), action: {}) } } .padding(.horizontal) .padding([.top, .bottom], 20) } } }
So far so good. The menu displays properly. But this is where I've hit a snag. I can't figure out a way to find which button user taps on.
I could include the menu type inside the MenuButton view itself and pass it back in the button tap closure.
Code Block swift struct MenuButton: View { let item: Menu.UserType var action: (_ item: Menu.UserType) -> Void var body: some View { Button(action: { action(item) }) { // ... } // ... } }
But this makes the MenuButton view couple of one menu type and not reusable.
So I was wondering if there's another, better way to handle this. Any suggestions, ideas would be appreciated.
Thanks.
In Swift, nested type does not work as a constraining something, but is just a namespace.But it's giving me the following error.
If you want to put some constraint for the generic argument, you may need to introduce another protocol:
Code Block protocol MenuItem {} extension Menu.UserType: MenuItem {} extension Menu.Main: MenuItem {} struct MenuButton<ItemType: MenuItem>: View { //... }