How can I read and get access to mouse cursor location in a view of macOS SwiftUI? - swift

Here is my very simple and starter project, as you can see in question title I want to read the mouse cursor location when it hover or move on my view in macOS SwiftUI.
For starter project I used gesture! The issue with gesture is there that the user should do some kind of tap or drag and that would trigger the gesture, but I want to get all benefit of gesture without having to make a tap or drag, how can I do this?
struct ContentView: View {
#State private var location: CGPoint = .zero
var body: some View {
return VStack {
Spacer()
HStack {
Spacer()
Text("location is: ( \(location.x), \(location.y))").bold()
Spacer()
}
Spacer()
}
.background(Color.yellow)
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onEnded { value in location = value.location })
}
}

try using the code below, note that it will provide the current overall mouse position inside the screen, not only inside the window.
However you can use the method .onHover in order to know when the mouse is over a view.
struct ContentView: View {
var mouseLocation: NSPoint { NSEvent.mouseLocation }
#State var isOverContentView: Bool = false
var body: some View {
ZStack{
//Place your view
Image(systemName: "applelogo")
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.onHover{ on in
isOverContentView = on
}
}
.frame(width: 600, height: 600)
.onAppear(perform: {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
print("\(isOverContentView ? "Mouse inside ContentView" : "Not inside Content View") x: \(self.mouseLocation.x) y: \(self.mouseLocation.y)")
return $0
}
})
}
}

Related

How can I track scrolling with a ScrollView linked to a Custom PageControl - SwiftUI

I want to create a Carousel with SwiftUI(without using TabView)
with a matching/linked Page Control in SwiftUI
So far I have both views and can update the pageControl view with a
#State var pagecontrolTracker updated with a DragGesture() .onChanged but it doesn't update the PageControl if I scroll fast, or sometimes doesn't update at all 😭.
If I Scroll slow tho, the Page Control does update sometimes as expected.
Is there a better way to update this faster and smoother?
I saw .updating modifier for DragGesture() but this doesn't work either
Full View:
struct ContentView: View {
#State var pagecontrolTracker: Int = 0
var body: some View {
VStack {
ScrollView(.horizontal) {
HStack {
ForEach(0...3, id: \.self) { index in
PagingRow()
.gesture(DragGesture().onChanged({ _ in
pagecontrolTracker = index
}))
}
}
}
PagingControls(pagecontrolTracker: $pagecontrolTracker)
}
.padding()
}
}
Inside Custom SwiftUI Row View
struct PagingRow: View {
var body: some View {
VStack {
HStack {
Image(systemName: "globe")
Text("Test Title")
}
.padding()
Button {
print("Test action")
} label: {
Text("Tap Me")
}
.buttonStyle(.borderedProminent)
.padding()
}
.background(Color.orange)
.frame(width: 200)
.cornerRadius(8)
}
}
Custom PageControl in SwiftUI
struct PagingControls: View {
#Binding var pagecontrolTracker: Int
var body: some View {
HStack {
ForEach(0...3, id: \.self) { pagingIndex in
Circle()
.fill(pagecontrolTracker == pagingIndex ? .orange : .black)
.frame(width: 8, height: 8)
}
}
}
}
Note: I don't want to use TabView since I want to be able to show the next upcoming card in the scrollView
A TabView would only show one card per page

SwiftUI Swipe/Drag over NavigationLink

I want to be able to update a view with a swipe left/right over a NavigationView that has multiple NavigationLinks in it. The NavigationLinks go to different presentation views.
When I begin the Drag gesture on the outer most view, if that gesture begins over a NavigationLink, the link changes color as though it has been pressed. Continuing the drag gesture, the main view does update as expected and the NavigationLink returns to it's normal state (color).
What I need is a way to have the NavigationLink NOT change color when it is a drag gesture that is occuring. Maybe a way to have the NavigationLink react "if" the touch is a long touch or something.
Here is some code that demonstrates what I am seeing. This is not my actual project, but a very stripped down example.
Any suggestions or solutions appreciated!
import SwiftUI
struct ContentView: View {
#State var outputText: String = ""
var body: some View {
VStack(alignment: .center, spacing: 20) {
NavigationView {
VStack {
Text("Navigation View")
NavigationLink(destination: Text("Showwing Widget")) {
HStack {
Text("Navigation Link")
}
.frame(width: 300, height: 200)
.border(Color.blue)
}
.border(Color.red)
Spacer()
}
.border(Color.yellow)
}
Text(outputText)
.font(.title)
.fontWeight(.bold)
Text("My Green Oval")
.foregroundColor(.white)
.fontWeight(.bold)
.font(.title)
.frame(width: 300, height: 200)
.background(
Ellipse()
.fill(Color.green)
)
Button(action: {
outputText = "Button tapped"
}) {
Text("Button to Tap")
}
Text("Just some words...")
Spacer()
}
.highPriorityGesture(DragGesture(minimumDistance: 25, coordinateSpace: .local)
.onEnded { value in
if abs(value.translation.height) < abs(value.translation.width) {
if abs(value.translation.width) > 50.0 {
if value.translation.width < 0 {
self.swipeRightToLeft()
} else if value.translation.width > 0 {
self.swipeLeftToRight()
}
}
}
}
)
}
func swipeRightToLeft() {
outputText = "Swiped Right to Left <--"
}
func swipeLeftToRight() {
outputText = "Swiped Left to Right -->"
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
After toying around forever I have a solution here! Hacky maybe, but it works with little effort. Configure your view with an offset that is conditional on whether your drawer row is open or not and also create a state variable to keep track on whether or not your user is dragging. You can get the screenWidth by using UIScreen.main.bounds.width.
.offset(x: self.isOpen ? -screenWidth/12 : 0, y: 0)
.simultaneousGesture(DragGesture()
.onChanged{ gesture in
self.isDragging = true
self.offset = gesture.translation.width
}
.onEnded { _ in
self.isDragging = false
if self.offset > 0 {
withAnimation {
self.isOpen = false
self.offset = 0
}
} else if self.offset < 0 {
withAnimation {
self.isOpen = true
self.offset = 0
}
}
})
Then add this disable modifier on your NavigationLink
.disabled(self.isDragging || self.isOpen)
Good luck! Hopefully this works for you as well if you haven't found a solution.

SwiftUI - How to close a fake Modal View - from the second view inside it, with a close button?

I have a tricky design. I'm trying to have a close button that from whatever views, inside a "fake" Modal View (fake cause it's full screen, thanks to a code that i found online), it's gonna close the Modal View.
Right now the close button is working in the first view that open in the modal view, but I need it to work also in the next views of the modal view, cause I have a flow inside this modal view to create an item.
This is the View from which I start ColorNewItemView, my fake Modal View.
struct RecapItemToAdd: View {
#State var isPresented: Bool = false
var body: some View {
NavigationView {
VStack {
Button(action: { withAnimation { self.isPresented.toggle()}}) {
Image(systemName: "pencil")
.resizable()
.renderingMode(.original)
.frame(width: 13, height: 17)
.foregroundColor(UIManager.hLightGrey)
}
}
ZStack {
VStack(alignment: .leading) {
ColorNewItemView(isPresenteded: self.$isPresented)
Spacer()
}
}
.background(Color.white)
.edgesIgnoringSafeArea(.all)
.offset(x: 0, y: self.isPresented ? 0 : UIApplication.shared.keyWindow?.frame.height ?? 0)
}
}
}
Note: I know that "keyWindow" is deprecated but I don't know how to change it.
With ColorNewItemView starts my full screen Modal View. In this view the close button works.
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresenteded: Bool
var body: some View {
NavigationStackView {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("First View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: Color2NewItemView(isPresenteded: self.$isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Avanti")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
}
}
}
}
Now I have the next view inside the Modal view, where the close button starts to stop working.
struct Color2NewItemView: View {
#Binding var isPresenteded: Bool
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("Second View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: FabricNewItemView(isPresenteded: $isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Tessuto")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
Spacer()
.frame(height: 18)
PopView{
Text("Back")
.font(UIManager.einaBodySemibold)
.foregroundColor(UIManager.hGrey)
}
}
}
}
Ps.
I had also to use a library called NavigationStack, since I have a custom back button on the bottom of the page, and the Navigation View doesn't let me pop back without using the back in the navigation bar.
Binding can be lost on deep view hierarchy, so it is more appropriate to operate with it on the level it was received.
Here is possible approach using EnvironmentKey (by same idea as presentationMode works)
Introduce helper environment key which holds some closure
struct DismissModalKey: EnvironmentKey {
typealias Value = () -> ()
static let defaultValue = { }
}
extension EnvironmentValues {
var dismissModal: DismissModalKey.Value {
get {
return self[DismissModalKey.self]
}
set {
self[DismissModalKey.self] = newValue
}
}
}
so in your top modal view you can inject into hierarchy callback to dismiss
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresented: Bool
var body: some View {
NavigationStackView {
// ... other code
}
.environment(\.dismissModal, { self.isPresented = false} ) // << here !!
}
}
thus this environment value now is available for all subviews, and you can use it as
struct Color2NewItemView: View {
#Environment(\.dismissModal) var dismissModal
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.dismissModal() // << here !!
}) {
// ... other code
}
}

How to know if element comes out the screen in swiftUI?

I try to know if an element comes out the screen in my application. I see that when my element it outside the screen, onDesappear is not trigger. I don't know if there is an other solution to trigger this?
I made an example to explain what I want. I have a circle with an offset to force it out of the screen to a certain degree:
struct ContentView: View {
#ObservedObject var location: LocationProvider = LocationProvider()
#State var heading: Double = 0
var body: some View {
ZStack {
Circle()
.frame(width: 30, height: 30)
.background(Color.red)
.clipShape(Circle())
.foregroundColor(Color.clear)
.offset(y: 300)
.border(Color.black)
.rotationEffect(.degrees(self.heading))
.onReceive(self.location.heading) { heading in
self.heading = heading
}
.onDisappear(perform: { print("Desappear") })
Text("\(self.heading)")
}
}
}
Maybe it's possible with geometryReader?

How to get mouse location with SwiftUI?

I am trying to make a popover display in SwiftUI.
I already have this going, but is there a way to make my popoverView appear where I clicked? For this I think i need to get the mouse location. How do i do this?
With help from SwiftUILab#twitter
struct ContentView: View {
#State private var pt: CGPoint = .zero
var body: some View {
let myGesture = DragGesture(minimumDistance: 0, coordinateSpace: .global).onEnded({
self.pt = $0.startLocation
})
// Spacers needed to make the VStack occupy the whole screen
return VStack {
Spacer()
HStack {
Spacer()
Text("Tapped at: \(pt.x), \(pt.y)")
Spacer()
}
Spacer()
}
.border(Color.green)
.contentShape(Rectangle()) // Make the entire VStack tappabable, otherwise, only the areay with text generates a gesture
.gesture(myGesture) // Add the gesture to the Vstack
}
}