Can't show dropdown menu over HStack in SwiftUI - swift

I have a list item that needs to have a dropdown menu that shows on top of the other views in the HStack.
This is what I have currently:
struct ListRow: View {
#Binding var item: Item
#State var showDropdown: Bool = false
var body: some View {
ZStack {
HStack {
VStack {
Text(item.name)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .topLeading)
ProgressView(value: item.percentDone)
}.padding(.all, 10)
Button(action: {
self.showDropdown.toggle()
}, label: {
Image(systemName: "ellipsis.circle")
}).buttonStyle(BorderlessButtonStyle())
.overlay (
VStack {
if self.showDropdown {
Spacer(minLength: 40)
Text("TEST")
.onTapGesture {
print("Delete")
}
}
}.zIndex(10)
)
}
}
}
}
But when I run it and click the button this is what I see
It looks like the Text is not showing due to a view overflow/overlap with the rest of the HStack, but I thought that adding the .zIndex would place the dropdown on top of the rest of the views in the ZStack

Just set frame for your overlay view to make it exceed its main view 's size
.overlay (
VStack {
if self.showDropdown {
Spacer(minLength: 40)
Text("TEST")
.onTapGesture {
print("Delete")
}
}
}.frame(width: 100)
)
Or you can try another approach like ZStack or Menu (as you mentioned a better solution)

I figured out a better solution.
struct ListRow: View {
#Binding var item: Item
var body: some View {
HStack {
VStack {
Text(item.name)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .topLeading)
ProgressView(value: item.percentDone)
}.padding(.all, 10)
Menu {
Button("Delete", action: {
print("Delete")
})
} label: {
}
.menuStyle(BorderlessButtonMenuStyle())
.frame(width: 10, height: 10, alignment: .center)
}
}
}

Related

Why does my custom tab bar get pushed up everytime the on screen keyboard pops up?

I've made a custom tab bar and I'm trying to add a search bar on one of my views. For some reason half of the tab gets pushed up when the onscreen keyboard appears. Ive tried " .ignoresSafeArea(.keyboard)" literally everywhere and the only thing that happens is my icons for the tab disappear but the top of the tab still stays there. I've been trying to fix it for the last 24 hours but I'm getting nowhere, can somebody please help me with this. Thanks!
Code for Custom Tab Bar:
import SwiftUI
enum Tabs: Int {
case home = 0
case hot = 1
case favourites = 2
case settings = 3
}
struct TabBar: View {
#Binding var selectedTab : Tabs
var body: some View {
HStack (alignment: .center){
Button {
//switch to home
selectedTab = .home
} label: {
GeometryReader { geo in
if selectedTab == .home {
Rectangle()
// .ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "house")
// .ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}
// .ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
//.ignoresSafeArea(.keyboard)
}
// .ignoresSafeArea(.keyboard)
.tint(Color.black)
Button {
//Switch views
selectedTab = .hot
} label: {
GeometryReader { geo in
if selectedTab == .hot {
Rectangle()
//.ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "flame")
//.ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}
//.ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
}
//.ignoresSafeArea(.keyboard)
.tint(Color.black)
Button {
//Switch views
selectedTab = .favourites
} label: {
GeometryReader { geo in
if selectedTab == .favourites {
Rectangle()
//.ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "star")
//.ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}//.ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
}
//.ignoresSafeArea(.keyboard)
.tint(Color.black)
Button {
//Switch views
selectedTab = .settings
} label: {
GeometryReader { geo in
if selectedTab == .settings {
Rectangle()
//.ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "gear")
//.ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}//.ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
}
//.ignoresSafeArea(.keyboard)
.tint(Color.black)
}
//.ignoresSafeArea(.keyboard, edges: .all)
.frame(height: 20)
}
}
struct TabBar_Previews: PreviewProvider {
static var previews: some View {
TabBar(selectedTab: .constant(.home))
.ignoresSafeArea(.keyboard)
}
}
ignoresSafeArea is commented in all the places I tried to put it.
Here is also the code for the search bar, maybe I need to input ignoreSafeArea somewhere here?
import SwiftUI
struct SearchBarView: View {
#StateObject var im = SearchBarContents()
//#State var selectedTabs: SearchB = .search
#State private var query = ""
var body: some View {
NavigationView {
List {
ForEach(im.filteredData) { item in
HStack{
NavigationLink(destination: ItemView(item: item))
{
SBarView(item: item)
}
}
}
}
.navigationTitle("Items")
.searchable(text: $query,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Find an Item") {
}
.onSubmit(of: .search) {
im.search(with: query)
}
.onChange(of: query) { newQuery in
im.search(with: newQuery)
}
.onAppear {
im.search()
}
}
}
}
struct SearchBarView_previews: PreviewProvider {
static var previews: some View {
SearchBarView()
}
}
If there is anything missing please let me know.
Imgur link to see exactly what I mean
https://imgur.com/fv79bKh
https://imgur.com/a/Rx9Ki6c (after I add ignoresafearea)
Depends on how you have set up your project. If you have a RootView which is controlling the View to be displayed based on the selection of your Tab Bar and is also the location in the project that you would add the Custom Tab Bar, then on that root View add the modifier .ignoresSafeArea(.keyboard)
For example:
enum Tabs: Int {
case home = 0
case hot = 1
case favourites = 2
case settings = 3
}
struct RootView: View {
#State private var selectedTab = Tabs.home
var body: some View {
VStack {
switch selectedTab {
case .home:
HomeView()
case .hot:
HotView()
case .favourites:
FavouritesView()
case .settings:
SettingsView()
}
Spacer()
CustomTabBar(selectedTab: $selectedTab)
}
.ignoresSafeArea(.keyboard)
}
}

How to do a "reveal"-style collapse/expand animation in SwiftUI?

I'd like to implement an animation in SwiftUI that "reveals" the content of a view to enable expand/collapse functionality. The content of the view I want to collapse and expand is complex: It's not just a simple box, but it's a view hierarchy of dynamic height and content, including images and text.
I've experimented with different options, but it hasn't resulted in the desired effect. Usually what happens is that when I "expand", the whole view was shown right away with 0% opacity, then gradually faded in, with the buttons under the expanded view moving down at the same time. That's what happened when I was using a conditional if statement that actually added and removed the view. So that makes sense.
I then experimented with using a frame modifier: .frame(maxHeight: isExpanded ? .infinity : 0). But that resulted in the contents of the view being "squished" instead of revealed.
I made a paper prototype of what I want:
Any ideas on how to achieve this?
Something like this might work. You can modify the height of what you want to disclose to be 0 when hidden or nil when not so that it'll go for the height defined by the views. Make sure to clip the view afterwards so the contents are not visible outside of the frame's height when not disclosed.
struct ContentView: View {
#State private var isDisclosed = false
var body: some View {
VStack {
Button("Expand") {
withAnimation {
isDisclosed.toggle()
}
}
.buttonStyle(.plain)
VStack {
GroupBox {
Text("Hi")
}
GroupBox {
Text("More details here")
}
}
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
HStack {
Text("Cancel")
Spacer()
Text("Book")
}
}
.frame(maxWidth: .infinity)
.background(.thinMaterial)
.padding()
}
}
No, this wasn't trying to match your design, either. This was just to provide a sample way of creating the animation.
Consider the utilization of DisclosureGroup. The following code should be a good approach to your idea.
struct ContentView: View {
var body: some View {
List(0...20, id: \.self) { idx in
DisclosureGroup {
HStack {
Image(systemName: "person.circle.fill")
VStack(alignment: .leading) {
Text("ABC")
Text("Test Test")
}
}
HStack {
Image(systemName: "globe")
VStack(alignment: .leading) {
Text("ABC")
Text("X Y Z")
}
}
HStack {
Image(systemName: "water.waves")
VStack(alignment: .leading) {
Text("Bla Bla")
Text("123")
}
}
HStack{
Button("Cancel", role: .destructive) {}
Spacer()
Button("Book") {}
}
} label: {
HStack {
Spacer()
Text("Expand")
}
}
}
}
The result looks like:
I coded this in under 5 minutes. So of course the design can be optimized to your demands, but the core should be understandable.
import SwiftUI
struct TaskViewCollapsible: View {
#State private var isDisclosed = false
let header: String = "Review Page"
let url: String
let tasks: [String]
var body: some View {
VStack {
HStack {
VStack(spacing: 5) {
Text(header)
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.black)
.padding(.top, 10)
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
Text(url)
.font(.system(size: 12, weight: .regular))
.foregroundColor(.black.opacity(0.4))
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
Image(systemName: self.isDisclosed ? "chevron.up" : "chevron.down")
.padding(.trailing)
.padding(.top, 10)
}
.onTapGesture {
withAnimation {
isDisclosed.toggle()
}
}
FetchTasks()
.padding(.horizontal, 20)
.padding(.bottom, 5)
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.black.opacity(0.2))
)
.frame(maxWidth: .infinity)
.padding()
}
#ViewBuilder
func FetchTasks() -> some View {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0 ..< tasks.count, id: \.self) { value in
Text(tasks[value])
.font(.system(size: 16, weight: .regular))
.foregroundColor(.black)
.padding(.vertical, 0)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.frame(maxHeight: CGFloat(tasks.count) * 20)
}
}
struct TaskViewCollapsible_Previews: PreviewProvider {
static var previews: some View {
TaskViewCollapsible(url: "trello.com", tasks: ["Hello", "Hello", "Hello"])
}
}

How to set up SwiftUI toolbar to display in the center of the view

I am experimenting with SwiftUI ToolbarItem in various positions in my view, such as in bottomBar and navigation. I am wondering if it is possible to center the ToolBarItem vertically in the view, as opposed to the top or bottom. When setting up the placement for ToolBarItem, I am not seeing a placement property for centering. Any idea how this ToolBarItem could be centered in the view?
Here is my code for the ToolBarItem, currently set to .bottomBar:
struct ContentView: View {
#State private var cityName = ""
var body: some View {
NavigationView {
VStack() {
//some content
}.navigationTitle("Weather")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {
// some action
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Try this approach for placing your toolbar elements in the middle of the ContentView.
It should look exactly like the .toolbar {...} you have, and it functions exactly as you expect.
The main difference with this approach is that
you can put the toolbar anywhere you like.
You can also use GeometryReader for very fine placement in your view.
struct ContentView: View {
#State private var cityName = ""
var toolbar: some View {
HStack {
Spacer()
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 110, maxWidth: 140, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {}) {
Image(systemName: "plus").font(.title)
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
Spacer()
}
}
var body: some View {
NavigationView {
VStack {
Spacer()
toolbar
Spacer()
}.navigationTitle("Weather")
}
}
}

Filling HStack swiftUI

I want to create a button stack like having a play and record button using SwiftUI, after creating it just does not look anything like what I wanted.
var body: some View {
HStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 8) {
Spacer()
Button(action: {
print("Recordinggg")
}, label: {
Text("Record")
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(40.0)
})
Spacer()
Button(action: {
print("Recordinggg")
}, label: {
Text("Play")
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(40.0)
})
Spacer()
}
}
What I actually want is something like this
Use proper frame, padding you can achieve this. Here is an example code.
Create ButtonStyle.
struct ThemeButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding([.top, .bottom], 10)
.foregroundColor(.white)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.background(Color.blue)
.cornerRadius(40.0)
}
}
Your view
struct ContentView: View {
#State private var phase: CGFloat = 0
var body: some View {
HStack(alignment: .center, spacing: 0) {
Button(action: {
print("Recordinggg")
}, label: {
Text("Record")
})
.buttonStyle(ThemeButtonStyle())
Spacer()
Button(action: {
print("Recordinggg")
}, label: {
Text("Play")
})
.buttonStyle(ThemeButtonStyle())
}
.padding()
}
}

SwiftUI: Detecting selection change in NavigationView on tvOS

I am building a SwiftUI application for tvOS and am currently trying to implement UI. I have NavigationView at the bottom and label at the top and I want label to show which NavigationLink is currently in focus. Here is my code:
#State private var selection: String? = nil
.
ZStack {
Color.red.edgesIgnoringSafeArea(.all)
VStack {
Text(selection ?? "no value").background(Color.green)
NavigationView {
ScrollView(.horizontal) {
HStack{
VStack {
NavigationLink(destination: view2, tag: "1", selection: $selection) {
Image("placeholder")
.scaledToFit().frame(width:400, height: 225) }
Text("Button")
}
VStack {
NavigationLink(destination: view2, tag: "2", selection: $selection) {
Image("placeholder")
.scaledToFit().frame(width:400, height: 225) }
Text("Button")
}
...
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .bottomLeading)
}
}
}
}
However, label value doesn't change when I change selection and shows no value:
Any ideas what should I do there?
I believe your Image is taken up selection effect of your NavigationLink a simple fix would be adding a .padding() to your Image. Here is some good examples you could use to customize the NavigationLink selection. More specifically the .buttonStyle of the NavigationLink.
var body: some View {
ZStack {
VStack{
NavigationView {
ScrollView(.horizontal){
HStack(spacing: 90) {
VStack{
NavigationLink(destination: ResultView(choice: "Chaka")) {
Image("chaka")
.scaledToFit()
.frame(width:400, height: 225)
.padding(20)
Text("Choose Chaka")
}
}
VStack{
NavigationLink(destination: ResultView(choice: "Dr Rick Marshall")) {
Image("rick_marshall")
.scaledToFit().frame(width:400, height: 225)
.frame(width:400, height: 225)
.padding(20)
Text("Padding 20").foregroundColor(Color.white)
} .buttonStyle(DefaultButtonStyle()) // buttonStyle allows for selection customiaztion ie color
}
VStack{
NavigationLink(destination: ResultView(choice: "Will Stanton")) {
Image("danny_mcBride")
.scaledToFit().frame(width:400, height: 225)
.frame(width:400, height: 225)
Text("No Padding").foregroundColor(Color.white)
} .buttonStyle(DefaultButtonStyle())
}
}.padding(60) // allows for selection pop out effect to be seen
}
.navigationBarTitle("Navigation")
}
}
}
}
Try this:
VStack {
NavigationLink(destination: view2, tag: "2", selection: $selection) {
Image("placeholder")
.scaledToFit().frame(width:400, height: 225) }
Text("Button")
}.onTapGesture{ selection = "2" }
}
The onTapGesture will override the tap on the NavigationLink, but will have the same effect.