How to avoid Bottom View to come over the Keyboard in SwiftUI? - swift

My View is something like this:
VStack{
ScrollView {
ForEach(0..<50) {_ in
Text("Editor")
}
VStack {
TextField("My Text Filed, text: $text)
}
.frame(minHeight: 200, maxHeight: 200)
.overlay(
RoundedRectangle(cornerRadius: 6.0)
.stroke(.gray, lineWidth: 1.0)
)
.padding()
}
Button("My Button")
.background(.green)
.padding()
}
My problem is that whenever clicking on text field my Button comes on the top of keyboard. I know this is cool feature and expected that view out side if scroll will come to top of keyboard. I want to avoid this, can any body give some solution how to avoid this.
I want my Button to always be below the ScrollView in the bottom not on the top of keyboard

Try the below code:
#State private var text = ""
var body: some View {
ZStack{
VStack{
Spacer()
Button("Button title") {
print("Button tapped!")
}
}
.ignoresSafeArea(.keyboard)
ScrollView {
ForEach(0..<50) {_ in
Text("Editor")
}
VStack {
TextField("My Text Filed", text: $text)
}
.frame(minHeight: 200, maxHeight: 200)
.overlay(
RoundedRectangle(cornerRadius: 6.0)
.stroke(.gray, lineWidth: 1.0)
)
.padding()
}
.padding(.bottom, 20)
}
}

Related

Why the button doesn't work in SwiftUI when it is located in some condition

Why the button doesn't work in this code by using SwiftUI?
I can't click the button.
When I lay the button on the top in VStack, it can work.
But when it is in below code, it can't work properly.
Is this related to VStack, TabView or button's overlay?
I think it is caused by button's offset and padding & tabview's padding.
Can I change it to ZStack in all same layout?
Please tell me how to do.
Thank you.
struct Profile: View {
#State private var selection = 0
var body: some View {
VStack{
VStack{
HStack{
Spacer()
Image(systemName: "person.circle")
Spacer()
}
HStack{
Text("|")
.foregroundColor(.white)
Image(systemName: "birthday.cake")
Text("birthDay")
}
}
.frame(height: 300)
.padding(.bottom,50)
Button(action: {
print("hello")
}){
Text("edit")
.fontWeight(.semibold)
.frame(width: 200, height: 48)
.foregroundColor(Color(.red))
.background(Color(.white))
.cornerRadius(24)
.overlay(
RoundedRectangle(cornerRadius: 24)
.stroke(Color(.red), lineWidth: 1.0)
)
.offset(y: -35)
.padding(.bottom, -20)
.contentShape(RoundedRectangle(cornerRadius: 20))
}
TabView(selection: $selection) {
VStack{
HStack{
Text("note")
}
}
.tag(0)
VStack{
Text("phone")
}
.tag(1)
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.frame(height:200)
.padding(.top,-60)
.padding(.horizontal)
}
}
}
The padding
.padding(.top,-60)
is covering your button.
Add the below line just after the padding, you'll see tabview covering the button with green transparent background.
.background(Color.green.opacity(0.4))
Just remove the .padding(.top,-60) and the button will click.
Add "Spacer()" underneath the Button
Spacer()
Spacer between Button() and Tabview() will enable the button to be clickable.

SwiftUI Centering Image between Views

I have an HStack that I am using as a tool bar. The HStack contains a button on the left, image in the middle, and then another HStack containing 2 buttons. Everything looks fine except for the image in the middle, it's slightly to the left. How can I get the image centered?
var toolbar: some View {
HStack(alignment: .center) {
Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.tint(.black)
}
Spacer()
Image("nav_logo")
.resizable()
.frame(width: 30, height: 30)
Spacer()
optionsButton
}
.frame(height: 30)
}
var optionsButton: some View {
HStack {
Button {
UIPasteboard.general.string = viewModel.take.shareLinkString
self.buildBottomAlert(type: .linkCopied)
} label: {
Image("shareIcon")
}
.padding(.trailing, 10)
Button {
showingAlert = true
} label: {
Image("moreMenu")
}
}
The image in the middle is offset because the first button and optionsButton do not have the same width. But the SpacerĀ“s inside the containing Stack do have equal width.
To solve this you could wrap them in to a container that is big enough so both have the same width.
e.g.:
//GeometryReader to get the size of the container this is in
GeometryReader{proxy in
HStack(alignment: .center) {
//Hstack to wrap the button
HStack(){
Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.tint(.black)
}
// Spacer to move the button to the left
Spacer()
}
// depending on the use case you would need to tweak this value
.frame(width: proxy.size.width / 4)
Spacer()
Image(systemName: "square.and.arrow.up.trianglebadge.exclamationmark")
.resizable()
.frame(width: 30, height: 30)
Spacer()
HStack(){
Spacer()
optionsButton
}
.frame(width: proxy.size.width / 4)
}
.frame(height: 30)
}

How do I make an HStack of SwiftUI buttons have height?

I have a simple SwiftUI row of buttons to mimic a keyboard layout. Just can't get the buttons to fill the height of the HStack. I can't tell where I need to give something height so it will look like a keyboard. No matter where I try to add height, the buttons still are small.
struct KeyboardView: View {
let rows = [String.keyboardFKeyRow, String.keyboardNumKeyRow, String.alpha1KeyRow, String.alpha2KeyRow, String.alpha3KeyRow, String.arrowKeyRow]
var body: some View {
VStack() {
KeyRow(labels: String.keyboardFKeyRow).frame(height: 100)
KeyRow(labels: String.keyboardNumKeyRow).frame(height: 100)
KeyRow(labels: String.alpha1KeyRow).frame(height: 100)
KeyRow(labels: String.alpha2KeyRow).frame(height: 100)
KeyRow(labels: String.alpha3KeyRow).frame(height: 100)
KeyRow(labels: String.arrowKeyRow).frame(height: 100)
}
}
}
struct KeyRow: View {
var labels: [String]
var body: some View {
HStack() {
Spacer()
ForEach(labels, id: \.self) { label in
Button {
print("Key Clicked")
} label: {
Text(label.capitalized)
.font(.body)
.frame(minWidth: 60, maxWidth: 80, minHeight: 80, maxHeight: .infinity)
}
}
Spacer()
}
}
}
The .bordered button style on macOS has a hard-coded size and refuses to stretch vertically. If you need a different button height, either use the .borderless style or create your own ButtonStyle or PrimitiveButtonStyle, and (either way) draw the background yourself. For example, I got this result:
from this playground:
import PlaygroundSupport
import SwiftUI
PlaygroundPage.current.setLiveView(HStack {
// Default button style, Text label.
Button("a", action: {})
.background { Rectangle().stroke(.mint.opacity(0.5), lineWidth: 1).clipped() }
.frame(maxHeight: .infinity)
// Default button style, vertically greedy Text label.
Button(action: {}, label: {
Text("b")
.frame(maxHeight: .infinity)
})
.background { Rectangle().stroke(.mint.opacity(0.5), lineWidth: 1).clipped() }
.frame(maxHeight: .infinity)
Button(action: {}, label: {
Text("c")
.fixedSize()
.padding()
.frame(maxHeight: .infinity)
.background {
RoundedRectangle(cornerRadius: 10)
.fill(Color(nsColor: .controlColor))
}
}).buttonStyle(.borderless)
.background { Rectangle().stroke(.mint.opacity(0.5), lineWidth: 1).clipped() }
.frame(maxHeight: .infinity)
}
.background { Rectangle().stroke(.red.opacity(0.5), lineWidth: 1).clipped() }
.frame(height: 100)
)

Change the background color of a Navigation View in Swift 5

I have a Navigation View that looks like this:
NavigationView {
VStack {
Text("Choose ingredients")
.font(.title)
Text("to search for, ")
.font(.title)
Text("each on their own line:")
.font(.title)
TextEditor(text: $userInput)
.frame(width: 300, height: 200)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(.gray, lineWidth: 2)
)
.navigationTitle("Scan Labels")
.navigationBarTitleDisplayMode(.inline)
Button(action: {
isShowingScanner = true // Add a cancel button!
}, label: {
Label("Scan", systemImage: "barcode.viewfinder")
.padding()
.font(.bold(.title2)())
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(.blue, lineWidth: 2)
)
})
.padding()
}
But no matter how I've tried, I cannot change the background color. I've tried adding Color.color.ignoreSafeArea(), both under the NavigationView and VStack. I've also tried adding .background() to both. But neither are giving me the entire background. Thoughts?
try this approach, works very well for me:
NavigationView {
ZStack { // <-- here
Color.green.ignoresSafeArea() // <-- here
VStack {
//...
}
}
}

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"])
}
}