SwiftUI macOS sheet does not dismiss sometimes - macos

I have implemented a sheet to edit the values of a client.
It's normally possible to edit the client and close the sheet after pressing the OK-Button. But if the sheet is open for a longer time it is not possible to dismiss the sheet. Nothing happens and they only way to proceed is to quit the program.
Does anyone have an idea why this happens sometimes?
struct ContentView: View {
#State private var showingEditClient = false
var body: some View {
VStack{
HStack {
Button(action: showEditClientSheet) {
Text("Edit Client")
}
.sheet(isPresented: $showingEditClient) {
EditClientSheet()
}
}
}
.frame(minWidth: 400, minHeight: 400)
}
func showEditClientSheet(){
showingEditClient.toggle()
}
}
struct EditClientSheet: View {
#Environment(\.presentationMode) var presentationMode
#State private var name = "Max"
var body: some View {
VStack {
Form {
TextField("Name", text: $name)
}
HStack{
Button(action: cancel) {
Text("Abbrechen")
}
Button(action: editClient) {
Text("Ok")
}
}
}
.frame(minWidth: 200, minHeight: 200)
}
func editClient() {
NSApp.keyWindow?.makeFirstResponder(nil)
//Check if content is correct to save
if name != "" {
//store the changes
self.presentationMode.wrappedValue.dismiss()
}else {
//show Alert
}
}
func cancel() {
self.presentationMode.wrappedValue.dismiss()
}
}

Related

How to detect a single mouse down event in SwiftUI on macOS

I want to detect ONE mouse down and ONE mouse up event on my view (a simple Rectangle). Here is the code I already made. Unfortunately I got a lot of 'mouse down' and 'mouse up' on the console. This not what I want. I want just one 'mouse down' when the mouse is pressed on my rectangle and one 'mouse up' when the mouse is released.
var body: some View {
Rectangle()
.onAppear(perform: {
NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown]) { event in
print ("mouse down")
return event
}
NSEvent.addLocalMonitorForEvents(matching: [.leftMouseUp]) { event in
print ("mouse up")
return event
}
})
}
I found the solution by using a View modifier
//
// ContentView.swift
// OnPressedOnRelease
//
import SwiftUI
struct ContentView: View {
#State private var message: String = "Click on rectangle"
var body: some View {
VStack {
Image(systemName: "magicmouse")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("\(message)")
Rectangle()
.modifier(PressActions(
onPress: {
// Do something on press...
message = "Mouse down"
},
onRelease: {
// Do something on release...
message = "Mouse up"
}
))
}
.padding()
.frame(width: 200, height: 200)
}
}
struct PressActions: ViewModifier {
var onPress: () -> Void
var onRelease: () -> Void
func body(content: Content) -> some View {
content
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged({ _ in
onPress()
})
.onEnded({ _ in
onRelease()
})
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I've recently worked with Gestures and a good solution is to do this :
struct ContentView: View {
#State var dragGestureValue: DragGesture.Value?
var body: some View {
Rectangle()
.gesture(pressActionsGesture())
.padding()
}
private func pressActionsGesture() -> some Gesture {
return DragGesture(minimumDistance: 0)
.onChanged {
// If nil, it means its the beginning of the DragGesture
if dragGestureValue == nil {
print("Mouse Down")
}
// Gives DragGesture informations to our #State value
dragGestureValue = $0
}
.onEnded { _ in
print("Mouse Up")
dragGestureValue = nil
}
}
}

SwiftUI for macOS - trigger sheet .onDismiss problem

In a multiplatform app I'm showing a sheet to collect a small amount of user input. On iOS, when the sheet is dismissed, the relevant .onDismiss method is called but not on macOS.
I've read that having the .onDismiss in the List can cause problems so I've attached it to the button itself with no improvement. I've also tried passing the isPresented binding through and toggling that within the sheet itself to dismiss, but again with no success.
I am employing a NavigationView but removing that makes no difference. The following simplified example demonstrates my problem. Any ideas? Should I even be using a sheet for this purpose on macOS?
I just want to make clear that I have no problem closing the sheet. The other questions I found were regarding problems closing the sheet - I can do that fine.
import SwiftUI
#main
struct SheetTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ListView()
}
}
}
The List view.
struct ListView: View {
#State private var isPresented: Bool = false
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
.sheet(isPresented: $isPresented, onDismiss: {
doSomethingAfter()
}) {
TestSheetView()
}
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
}
func doSomethingBefore() {
print("Johnny")
}
func doSomethingAfter() {
print("Cash")
}
}
This is the sheet view.
struct TestSheetView: View {
#Environment(\.presentationMode) var presentationMode
#State private var name = ""
var body: some View {
Form {
TextField("Enter name", text: $name)
.padding()
HStack {
Spacer()
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
}
}
.frame(minWidth: 300, minHeight: 300)
.navigationTitle("Jedward")
}
}
Bad issue.. you are right. OnDismiss is not called. Here is a workaround with Proxybinding
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
.sheet(isPresented: Binding<Bool>(
get: {
isPresented
}, set: {
isPresented = $0
if !$0 {
doSomethingAfter()
}
})) {
TestSheetView()
}
}

SwiftUI background image moves when keyboard shows

I have an image background, which should stay in place when the keyboard shows, but instead it moves up together with everything on the screen. I saw someone recommend using ignoresSafeArea(.keyboard), and this question Simple SwiftUI Background Image keeps moving when keyboard appears, but neither works for me. Here is my super simplified code sample. Please keep in mind that while the background should remain unchanged, the content itself should still avoid the keyboard as usual.
struct ProfileAbout: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("write something", text: $text)
Spacer()
Button("SomeButton") {}
}
.background(
Image("BackgroundName")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea(.keyboard)
)
}
}
Here a possible salvation:
import SwiftUI
struct ContentView: View {
#Environment(\.verticalSizeClass) var verticalSizeClass
#State var valueOfTextField: String = String()
var body: some View {
GeometryReader { proxy in
Image("Your Image name here").resizable().scaledToFill().ignoresSafeArea()
ZStack {
if verticalSizeClass == UserInterfaceSizeClass.regular { TextFieldSomeView.ignoresSafeArea(.keyboard) }
else { TextFieldSomeView }
VStack {
Spacer()
Button(action: { print("OK!") }, label: { Text("OK").padding(.horizontal, 80.0).padding(.vertical, 5.0).background(Color.yellow).cornerRadius(5.0) }).padding()
}
}
.position(x: proxy.size.width/2, y: proxy.size.height/2)
}
}
var TextFieldSomeView: some View {
return VStack {
Spacer()
TextField("write something", text: $valueOfTextField).padding(5.0).background(Color.yellow).cornerRadius(5.0).padding()
Spacer()
}
}
}
u can use GeometryReader
get parent View size
import SwiftUI
import Combine
struct KeyboardAdaptive: ViewModifier {
#State private var keyboardHeight: CGFloat = 0
func body(content: Content) -> some View {
GeometryReader { geometry in
content
.padding(.bottom, keyboardHeight)
.onReceive(Publishers.keyboardHeight) {
self.keyboardHeight = $0
}
}
}
}
extension Publishers {
static var keyboardHeight: AnyPublisher<CGFloat, Never> {
let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
.map { $0.keyboardHeight }
let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
.map { _ in CGFloat(0) }
return MergeMany(willShow, willHide)
.eraseToAnyPublisher()
}
}
extension View {
func keyboardAdaptive() -> some View {
ModifiedContent(content: self, modifier: KeyboardAdaptive())
}
}
struct ProfileAbout: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("write something", text: $text)
Spacer()
Button("SomeButton") {}
}
.background(
Image("BackgroundName")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea(.keyboard)
)
.keyboardAdaptive()
}
}

How do I switch between screens in TabView and from the latter to the other View?

I created a simple collection with a button jump to the next View. From the last View there should be a transition to AddItemView, but it doesn't happen - it goes back to the first screen.
Can you tell me where I made a mistake?
What is the correct way to place the background Image on the first collection screen, so that it won't be on the following screens?
import SwiftUI
struct AddItemView: View {
var body: some View {
Text("Hallo!")
}
}
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
ForEach(0..<emojis.endIndex) { index in
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.endIndex {
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In this code, you have not to use NavigationView. It's required to navigate to the next screen. Similar concept like Push view controller if navigation controller exists. Also, remove endIndex and use indices.
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
NavigationView { //<- add navigation view
TabView(selection: $tabSelection) {
ForEach(emojis.indices) { index in //<-- use indices
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.count - 1 { //<- use count
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
}
If you have already a navigation link from the previous screen then, the problem is you are using endIndex in the wrong way. Check this thread for correct use (https://stackoverflow.com/a/36683863/14733292).

Has anyone updated SwiftUI Popovers to work in Xcode beta-3?

Updated to Xcode beta-3, Popover was deprecated... having one hell of a time trying to figure out how to make it work again!?!?
It no longer "pops up" it slides up from the bottom.
It's no longer positioned or sized correctly, takes up the whole screen.
Once dismissed, it never wants to appear again.
This was the old code, that worked perfectly...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
#State private var showPositions = false
var body: some View {
HStack {
Spacer()
Button(action: { self.showPositions = true } ) {
Text("Position")
}
.presentation(showPositions ? Popover(content: MultiPicker(items: Exercise.Position.allCases, selected:$filter.positions),
dismissHandler: { self.showPositions = false })
: nil)
}
.padding()
}
}
And this is the new code...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
#State private var showPositions = false
var body: some View {
HStack {
Spacer()
Button(action: { self.showPositions = true } ) {
Text("Position")
}
.popover(isPresented: $showPositions) {
MultiPicker(items: Exercise.Position.allCases, selected:self.$filter.positions)
.onDisappear { self.showPositions = false }
}
}
.padding()
}
}
I ended up using PresentationLink just so I can move forward with everything else...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
var body: some View {
HStack {
Spacer()
PresentationLink(destination: MultiPicker(items: Exercise.Position.allCases, selected:$filter.positions)) {
Text("Position")
}
}
.padding()
}
}
It works, as far as testing is concerned, but it's not a popover.
Thanks for any suggestions!
BTW, this code is being in the iPad simulator.
On OSX the code below works fine
struct ContentView : View {
#State var poSelAbove = false
#State var poSelBelow = false
#State var pick : Int = 1
var body: some View {
let picker = Picker(selection: $pick, label: Text("Pick option"), content:
{
Text("Option 0").tag(0)
Text("Option 1").tag(1)
Text("Option 2").tag(2)
})
let popoverWithButtons =
VStack {
Button("Not Dismiss") {
}
Divider()
Button("Dismiss") {
self.poSelAbove = false
}
}
.padding()
return VStack {
Group {
Button("Show button popover above") {
self.poSelAbove = true
}.popover(isPresented: $poSelAbove, arrowEdge: .bottom) {
popoverWithButtons
}
Divider()
Button("Show picker popover below") {
self.poSelBelow = true
}.popover(isPresented: $poSelBelow, arrowEdge: .top) {
Group {
picker
}
}
}
Divider()
picker
.frame(width: 300, alignment: .center)
Text("Picked option: \(self.pick)")
.font(.subheadline)
}
// comment the line below for iOS
.frame(width: 800, height: 600)
}
On iOS (iPad) the popover will appear in a strange transparent full screen mode. I don't think this is intended. I have added the problem to my existing bug report.

Resources