iOS16 Bug Keyboard breaks layout on sheet dismissal SwiftUI - swift

In iOS16 faced a bug with keyboard inside sheet, when sheet is dismissing keyboard disappears(what is ok), but layout is not updated. I saw only 1 question on same problem and wondering maybe somebody found a temporary workaround until Apple don't fix this.
Code to reproduce :
struct Test: View {
#State var isPresented: Bool = false
#State var text: String = ""
var body: some View {
VStack{
Button {
isPresented.toggle()
} label: {
Text("PRESENT")
}
}
.sheet(isPresented: $isPresented) {
ZStack {
Color.red
VStack{
TextField("Test", text: $text)
.frame(height: 50, alignment: .center)
Spacer()
Rectangle()
.fill(Color.blue)
.frame(width:300, height: 50)
}
}
}
}
}
Video:
https://vimeo.com/758845068

The .ignoresSafeArea() fixes the issue, but...
This will have as result of keyboard overlap in your UI and not be able to scroll to see all your elements.
I use the .adaptsToKeyboard() custom modifier taken from this answer
and then using it where needed with this particular order.
VStack {...}
.adaptsToKeyboard()
.ignoresSafeArea()

Related

After using the keyboard and going directly to the details, only half of the screen is displayed when you return

By using NavigationLink directly after activating the keyboard and returning to the page, the position of the keyboard appears with a white background and the space is compressed.
Here my own speculation may be that although the keyboard has been closed, but it seems that the page does not know, if you re-click the search to activate the keyboard, and then hit enter, the page will return to normal. It seems that Navigationlink skipped the normal keyboard closing step.
But now I'm not sure how to verify my suspicions and how to solve the problem. Here is part of my code, please can someone help me, thank you very much.
import SwiftUI
struct HomePageView: View {
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel: HomePageViewModel
var body: some View {
ZStack(alignment: .bottomTrailing) {
VStack{
SearchBar(draft: $viewModel.searchDraft, barType: .item)
ScrollView(showsIndicators: false) {
itemListComponent
}
}
.padding(.horizontal, 16)
addItemButton
}
.onTapGesture {
self.endTextEditing()
}
.sheet(isPresented: $viewModel.itemCreateViewIsShow) {
NavigationView {
ItemEditorView(ItemEditorViewModel(context))
}
}
.background(Color("background"))
.navigationTitle("appName".localized())
.navigationViewStyle(.stack)
}
#FetchRequest(fetchRequest: Item.fetchAllItems()) private var items: FetchedResults<Item>
#ViewBuilder
private var itemListComponent: some View {
HStack (alignment: .center, spacing: 0) {
Text("item.sort.storage".localized())
Spacer(minLength: 0)
}
.frame(height: 52)
LazyVStack {
ForEach(items) { item in
NavigationLink(
destination:ItemDetailView(item: item, isShowing: $viewModel.isItemDetailViewPresented)
) {
ItemCellView(item: item)
}
.isDetailLink(false)
}
}
}
private var addItemButton: some View {
Button {
viewModel.addItemButtonPressed()
} label: {
Image("plus.customize")
.resizable()
.scaledToFit()
.frame(width: 22, height: 22)
.padding(17)
.background(Color("primary"))
.clipShape(Circle())
}
.padding(.trailing)
.padding(.bottom)
}
}

Textfield not showing

I want to show a textfield on an image. To do so, I used a ZStack view.
But the textfield isn't showing up.
Here is my code :
var meme: some View {
ZStack {
image!
.resizable()
.scaledToFit()
TextField("Enter here", text: $topText)
.padding(.all)
.foregroundColor(.white)
}
}
Why is it doing this ?
The TextField is there, but just difficult to see. You can add a background color to it, similar to what it would look like in something like Snapchat. You will now be able to see it and know where to tap.
Code:
struct ContentView: View {
#State private var topText = ""
var body: some View {
ZStack {
Image("background")
.resizable()
.scaledToFit()
TextField("Enter here", text: $topText)
.padding(.all)
.foregroundColor(.white)
.background(Color.black.opacity(0.5))
}
}
}
Result:

When pressing button and toggling a Bool var all of my Bool vars toggle?

I have a form with several Boolean variables that change images on toggle, but when I click any of the buttons, all my variables change images? I have pulled all of them into separate subviews with the same result. Doesn't make any sense to me. Any help would be appreciated. Probably something simple I'm overlooking, but I'll be damned if I can see it.
Here is the code.
#State var total: String = ""
#State var date: Date = Date()
#State var bathroom: Bool = false
#State var steps: Bool = false
#State var furniture: Bool = false
#State var travel: Bool = false
#State var woodFloor: Bool = false
#State var concreteFloor: Bool = false
#State var takeUp: Bool = false
var body: some View {
VStack {
Form {
Section(header: Text("Job Info")
.font(.title2)
.foregroundColor(.blue)
.fontWeight(.bold)
.padding(.bottom, 10)
) {
VStack(alignment: .leading) {
DatePicker("Date", selection: $date)
.font(.title3)
.foregroundColor(.black)
.padding(.bottom, 10)
HStack(alignment: .center) {
Text("Total: $")
.font(.title3)
.foregroundColor(.black)
.padding(.trailing, 0)
TextField("", text: $total)
.font(.title3)
.frame(width: 100, height: 20, alignment: .leading)
.background(Color.yellow)
.foregroundColor(.black)
.cornerRadius(5)
Spacer()
}
.padding(.bottom, 20)
}
Section(header: Text("Addons")
.font(.title3)
.foregroundColor(.blue)
.padding(.bottom, 10)
) {
VStack(alignment: .leading, spacing: 5) {
Spacer()
HStack {
Text("Bathroom")
Spacer()
Button {
self.bathroom.toggle()
} label: {
Image(systemName: bathroom ? "checkmark.square" : "square")
}
}
HStack {
Text("Steps")
Spacer()
Button {
self.steps.toggle()
} label: {
Image(systemName: steps ? "checkmark.square" : "square")
}
}
HStack {
Text("Furniture")
Spacer()
Button {
self.furniture.toggle()
} label: {
Image(systemName: furniture ? "checkmark.square" : "square")
}
}
Spacer()
}
}
}
}
You put all buttons in one row (VStack with HStacks creates one view, so one row), and Form (being a List) sends all actions whenever any button is clicked in a row (it is designed to have one active element in a row).
So the solution would be either to remove VStack
Section(header: Text("Addons")
.font(.title3)
.foregroundColor(.blue)
.padding(.bottom, 10)
) {
VStack(alignment: .leading, spacing: 5) { // << this !!
Spacer()
and let every HStack with button live in own row...
... or instead of buttons use Image with tap gesture, like
HStack {
Text("Steps")
Spacer()
Image(systemName: steps ? "checkmark.square" : "square")
.padding()
.onTapGesture {
self.steps.toggle()
}
}
If you have a button inside a Section of Form the whole section will act as a button. So when you tap on it, all 3 button actions get executed.
instead, You can use Image with .onTapGesture{} modifier. In that way, you'll get what you want.
Sample code,
Image(systemName: bathroom ? "checkmark.square" : "square")
.foregroundColor(.blue)
.onTapGesture {
self.bathroom.toggle()
}
I think you are expecting the wrong thing from Form. Forms are not to make flexible lists with fancy stuff. If you want to be able to do anything you want and be able to customize your list at will, you should probably use Grids and other normal components like VStack...
How to use Form and what to expect:
The easiest way to see what you can do or not do with Forms without working around the limitations, is to go to the settings on your iPhone and see how different part of the settings are made. You can achieve almost all those stuff very easily without much work. However, if you want something even a little bit different than what you see in the settings, then you probably need a workaround to implement that in your app because Forms are not flexible.
Example of what you can do with Forms, taken from the settings:
For clarification, i am not saying you can't achieve what you want using forms. I'm saying thats most likely not a good idea to try to force forms to be something that they are not supposed to be.
The Answer:
So what should you do now? You should probably replace your button with Toggles so you get something similar to what you see in the settings.
[Other people have already said how to fix your current problem, so i'll just say the better way]
Consider using something like this:
struct ContentView: View {
#State var date = Date()
#State var total = ""
#State var bathroom = false
#State var steps = false
#State var furniture = false
var body: some View {
NavigationView { // DELETE this if the view before this view
// already has a navigation view
Form {
Section(header: Text("Job Info")) {
DatePicker("Date", selection: $date)
NavigationLink.init(
destination: FormTextFieldView(name: "Total $", value: $total),
label: {
Text("Total")
Spacer()
Text("$ " + total).foregroundColor(.secondary)
})
}
Section(header: Text("Addons")) {
Toggle("Bathroom", isOn: $bathroom)
Toggle("Steps", isOn: $steps)
Toggle("Furniture", isOn: $furniture)
}
}
.navigationBarTitle("Form", displayMode: .inline)
}
}
}
struct FormTextFieldView: View {
let name: String
#Binding var value: String
var body: some View {
Form {
TextField(name, text: $value)
}
.navigationBarTitle(name)
}
}
Why to use this?
First, because it is built with No Effort. You barely need to use any modifiers. So simple!
Second, this will work well on Any apple device. When you use modifiers, specially setting a frame for the view, you'll need to consider what will happen if you use e.g. an iPad. You don't need to worry about that when you are using this approach.

Problem with SwiftUI: Vertical ScrollView with Button

I'm quite new to programming so please excuse any dumb questions. I'm trying to make a ScrollView with the content being buttons. Although the button prints to console, when shown in the simulator the button displays as a large blue rectangle rather than displaying the image I would like it to.
Code Regarding ScrollView:
struct ContentView: View {
var body: some View {
[Simulator Display][1]
VStack {
Image("logo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.leading, 50)
.padding(.trailing, 50)
.padding(.top, 20)
.padding(.bottom, -20)
Spacer()
ScrollView {
VStack(spacing: 20) {
Button(action: {
//ToDo
print("Executed")
}) {
Image("Logo")
}
}
}
}
}
}
Simulator Display:
Image(Placeholder for now) I want to be displayed:
So I tried it an yeah it was very weird. Anyway, here is an example of how you can include the image. Just take the portion of the Button and paste it
struct ContentView: View {
var body: some View {
ZStack {
Button(action: {
print("button pressed")
}) {
Image("image")
.renderingMode(.original)
}
}
}
}

SwiftUI: Two buttons with the same width/height

I have 2 buttons in an H/VStack. Both of them contain some text, in my example "Play" and "Pause". I would like to have that both buttons have the same width (and height) determined by the largest button. I have found some answers right here at SO but I can't get this code working unfortunately.
The following code illustrates the question:
import SwiftUI
struct ButtonsView: View {
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
}
Button(action: { print("PAUSE tapped") }) {
Text("Pause")
}
}
}
}
struct ButtonsView_Previews: PreviewProvider {
static var previews: some View {
ButtonsView()
}
}
The tvOS preview from Xcode shows the problem:
I would be thankful for an explanation for newbies 🙂
Here is run-time based approach without hard-coding. The idea is to detect max width of available buttons during drawing and apply it to other buttons on next update cycle (anyway it appears fluently and invisible for user).
Tested with Xcode 11.4 / tvOS 13.4
Required: Simulator or Device for testing, due to used run-time dispatched update
struct ButtonsView: View {
#State private var maxWidth: CGFloat = .zero
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
Button(action: { print("PAUSE tapped") }) {
Text("Pause Long Demo")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
}
}
// helper reader of view intrinsic width
private func rectReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { gp -> Color in
DispatchQueue.main.async {
binding.wrappedValue = max(binding.wrappedValue, gp.frame(in: .local).width)
}
return Color.clear
}
}
}
You can implement the second custom layout example in the WWDC 2022 talk https://developer.apple.com/videos/play/wwdc2022/10056/ titled "Compose custom layouts with SwiftUI" which, if I understand the question, specifically solves it, for an arbitrary number of buttons/subviews. The example starts at the 7:50 mark.
after reading hit and trial implementing SO solns etc finally resolved this issue posting so that newbies as well as intermediate can benefit
paste it and obtain equal size(square) views
VStack(alignment: .center){
HStack(alignment:.center,spacing:0)
{
Button(action: {}, label: {
Text("Button one")
.padding(35)
.foregroundColor(.white)
.font(.system(size: 12))
.background(Color.green)
.frame(maxWidth:.infinity,maxHeight: .infinity)
.multilineTextAlignment(.center)
.cornerRadius(6)
}).background(Color.green)
.cornerRadius(6)
.padding()
Button(action: {}, label: {
Text("Button two")
.padding(35)
.foregroundColor(.white)
.font(.system(size: 12))
.frame(maxWidth:.infinity,maxHeight: .infinity)
.background(Color.green)
.multilineTextAlignment(.center)
}) .background(Color.green)
.buttonBorderShape(.roundedRectangle(radius: 8))
.cornerRadius(6)
.padding()
}.fixedSize(horizontal: false, vertical: true)
Add as many as buttons inside it. You can adjust it for VStack by adding only one button in hstack and add another button in another Hstack. I gave a general soln for both VStack and Hstack. You can also adjust padding of button as .padding(.leading,5) .padding(.top,5) .padding(.bottom,5) .padding(.trailing,5) to adjust the gaps between buttons
I think the best solution is to use GeometryReader, which resizes the width of the content of the Button. However, you need to check that you set a width of the Wrapper around the GeometryReader, because otherwise it would try to use the full screen width. (depends where you use that view, or if it is your primary view)
VStack
{
GeometryReader { geo in
VStack
{
Button(action: { print("PLAY tapped") }){
Text("Play")
.frame(width: geo.size.width)
}
.border(Color.blue)
Button(action: { print("Pause tapped") }){
Text("PAUSE")
.frame(width: geo.size.width)
}
.border(Color.blue)
}
}
}
.frame(width: 100)
.border(Color.yellow)
... which will look like that.
What happens if you put a Spacer() right after the Text("Play")? I think that might stretch out the 'Play' button.
Or maybe before and after Text("Play").