SwiftUI onDrag does not show preview image when running on device - drag-and-drop

I noticed an issue with the drag and drop features in SwiftUI that are available since iOS 13.4. The drag and drop operation with the .onDrag and .onDrop modifiers works fine in the simulator, but on a real device (iPhone and iPad) you just see a transparent rect, instead of the view while dragging the view.
Does anyone have a solution to get the correct preview image while the view is dragged?
struct MainView: View {
#State var isDropTarget = false
var body: some View {
VStack{
Image(systemName: "doc.text")
.font(.system(size: 40))
.frame(width: 150, height: 150)
.onDrag { return NSItemProvider(object: "TestString" as NSString) }
Color.orange
.opacity(isDropTarget ? 0.5 : 1)
.onDrop(of: ["public.text"], isTargeted: $isDropTarget) { items in
for item in items {
if item.canLoadObject(ofClass: NSString.self) {
item.loadObject(ofClass: String.self) { str, _ in
print(str ?? "nil")
}
}
}
return true
}
}
}

While iOS 15 did not fix this bug in my testing, there is a new API that allows you to specify the preview View to display: onDrag(_:preview:). You can recreate the view being dragged, in this case your Image, for the preview.

Related

How can fix the size of view in mac in SwiftUI?

I am experiencing a new change with Xcode Version 14.1 about view size in macOS, in older version if I gave a frame size or use fixedSize modifier, the view would stay in that size, but with 14.1 I see that view could get expended even if I use frame or fixedSize modifier, which is not what I expect. For example I made an example to show the issue, when I update the size to 200.0, the view/window stay in bigger size, which I expect it shrinks itself to smaller size, so how can I solve the issue?
struct ContentView: View {
#State private var sizeOfWindow: CGFloat = 400.0
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Button("update", action: {
if (sizeOfWindow == 200.0) { sizeOfWindow = 400.0 }
else { sizeOfWindow = 200.0 }
print(sizeOfWindow)
})
}
.frame(width: sizeOfWindow, height: sizeOfWindow)
.fixedSize()
}
}
Credit:
Credit to https://developer.apple.com/forums/thread/708177?answerId=717217022#717217022
Approach:
Use .windowResizability(.contentSize)
Along with windowResizability use one of the following:
if you want set size based on the content size use fixedSize()
If you want to hardcode the frame then directly set .frame(width:height:)
Code
#main
struct DemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.frame(width: 200, height: 200) //or use fixed size and rely on content
}
.windowResizability(.contentSize)
}
}

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

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

Show bottom sheet after button press swiftui

I'm trying to add to my app bottom sheet with responsive height which I can set programmatically. For this purpose I'm trying to use this video. Here is code of my view controller:
struct SecondView: View {
#State var cardShown = false
#State var cardDismissal = false
var body: some View {
Button {
cardShown.toggle()
cardDismissal.toggle()
} label: {
Text("Show card")
.bold()
.foregroundColor(Color.white)
.background(Color.red)
.frame(width: 200, height: 50)
}
BottomCard(cardShown: $cardShown, cardDismissal: $cardDismissal) {
CardContent()
}
}
}
struct CardContent:View{
var body: some View{
Text("some text")
}
}
struct BottomCard<Content:View>:View{
#Binding var cardShown:Bool
#Binding var cardDismissal:Bool
let content:Content
init(cardShown:Binding<Bool> , cardDismissal:Binding<Bool>, #ViewBuilder content: () -> Content){
_cardShown = cardShown
_cardDismissal = cardDismissal
self.content = content()
}
var body: some View{
ZStack{
//Dimmed
GeometryReader{ _ in
EmptyView()
}
.background(Color.red.opacity(0.2))
.opacity(cardShown ? 1 : 0)
.animation(.easeIn)
.onTapGesture {
cardShown.toggle()
}
// Card
VStack{
Spacer()
VStack{
content
}
}
.edgesIgnoringSafeArea(.all)
}
}
}
but after pressing the button I don't see any pushed bottom menu. I checked and it seems that I have similar code to this video but on the video bottom sheet appears. Maybe I missed something important for menu showing. The main purpose is to show bottom menu with responsive height which will wrap elements and will be able to change menu height. I tried to use .sheet() but this element has stable height as I see. I know that from the ios 15+ we will have some solutions for this problem but I would like to create something more stable and convenient :)
iOS 16
We can have native SwiftUI resizable sheet (like UIKit). This is possible with the new .presentationDetents() modifier.
.sheet(isPresented: $showBudget) {
BudgetView()
.presentationDetents([.height(250), .medium])
.presentationDragIndicator(.visible)
}
Demo:
This is what I got when running your code
I got this after some adjustments to bottom card
struct BottomCard<Content:View>:View{
#Binding var cardShown:Bool
#Binding var cardDismissal:Bool
let content:Content
init(cardShown:Binding<Bool> , cardDismissal:Binding<Bool>, #ViewBuilder content: () -> Content){
_cardShown = cardShown
_cardDismissal = cardDismissal
self.content = content()
}
var body: some View{
ZStack{
//Dimmed
GeometryReader{ _ in
EmptyView()
}
.background(Color.red.opacity(0.2))
.animation(.easeIn)
.onTapGesture {
cardShown.toggle()
}
// Card
VStack{
Spacer()
VStack{
content
}
Spacer()
}
}.edgesIgnoringSafeArea(.all)
.opacity(cardShown ? 1 : 0)
}
}
So you just need to set the height!
what you want to do is to have a card that only exists when there is a certain standard met.
If you want to push up a card from the bottom then you can make a view of a card and put it at the bottom of a Zstack view using a geometry reader and then make a button that only allows for that card to exist when the button is pressed INSTEAD of trying to hire it by changing its opacity. Also, make sure you move the dismissal button to the inside of the cad you have.
Heres an example you can try :
struct SecondView: View {
#State var cardShown = false
var body: some View {
GeometryReader{
ZStack {
ZStack{
// I would also suggest getting used to physically making your
//button and then giving them functionality using a "Gesture"
Text("Show Button")
.background(Rectangle())
.onTapGesture{
let animation = Animation.spring()
withAnimation(animation){
self.cardShown.toggle
}
}
}
ZStack {
if cardShown == true{
BottomCard(cardShown: $cardShown) {
CardContent()
}
}
// here you can change how far up the card comes after the button
//is pushed by changing the "0"
.offset(cardShown == false ? geometry.size.height : 0)
}
}
}
}
}
Also, you don't need to have a variable for the card being shown and a variable for the card being dismissed. Just have one "cardShown" variable and make it so that when it is TRUE the card is shown and when it is FALSE (after hitting the button on the card or hitting the initial button again.) the card goes away.
iOS 16.0+
iPadOS 16.0+
macOS 13.0+
Mac Catalyst 16.0+
tvOS 16.0+
watchOS 9.0+
Use presentationDetents(_:)
struct ContentView: View {
#State private var isBottomSheetVisible = false
var body: some View {
Button("View Settings") {
isBottomSheetVisible = true
}
.sheet(isPresented: $isBottomSheetVisible) {
Text("Bottom Sheet")
.presentationDetents([.height(250), .medium])
.presentationDragIndicator(.visible)
}
}
}

SwiftUI zIndex not updating correctly inside ForEach

This issue occurs in Xcode 11 GM.
When a view is tapped the colour will change to blue and the zIndex should be updated to move it to the front.
With the top example this works fine and you will never see a green view overlap a blue view. In the bottom example (using the ForEach) it doesn't redraw correctly and the blue view stays behind the green.
Although strangely if you have the bottom example in a bad state and then touch any of the views in top then the bottom will suddenly correct itself.
Any ideas or solution?
struct ContentView: View {
var body: some View {
ZStack {
// First example, working
TestView().position(x: 100, y: 100)
TestView().position(x: 200, y: 200)
// Second example, not working
ForEach(1..<3) { i in
TestView().position(x: 100*CGFloat(i), y: 300 + 100*CGFloat(i))
}
}
}
}
struct TestView: View {
#State var isTapped = false
var body: some View {
return Text("Test Test Test")
.frame(width: 150, height: 150)
.background(isTapped ? Color.blue : Color.green)
.zIndex(isTapped ? 100 : 0)
.gesture(
TapGesture()
.onEnded {
self.isTapped.toggle()
}
)
}
}

TapGesture is not working in Xcode 11.0 Beta

Playing around with SwiftUI and this TapGesture() (or any other gesture) doesn't seem to work for me in a Cocoa app for MacOS, despite #available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) when I View Definition on TapGesture.
import SwiftUI
struct CircleView : View {
var body: some View {
Circle()
.fill(Color.blue)
.frame(width: 400, height: 400, alignment: .center)
.gesture(
TapGesture()
.onEnded { _ in
print("View tapped!")
}
)
}
}
#if DEBUG
struct CircleView_Previews : PreviewProvider {
static var previews: some View {
CircleView()
}
}
#endif
The Build succeeded, I'm viewing the Circle in Preview and I have the console open, but nothing seems to print.
Am I doing something wrong? Is this a 10.15 Beta bug? Is there another framework I need to import other than SwiftUI? New to Swift here.
The tap is working fine, but when you’re previewing the app, you won’t see your print statements in the console. Actually run the app and you’ll see the print statements show up in your console.
Or change your app to present something in the UI confirming the tap gesture, e.g., an Alert:
struct CircleView : View {
#State var showAlert = false
var body: some View {
Circle()
.fill(Color.blue)
.tapAction {
self.showAlert = true
}
.presentation($showAlert) {
Alert(title: Text("View Tapped!"),
primaryButton: .default(Text("OK")),
secondaryButton: .cancel())
}
}
}
Or perhaps you would want to animate the change of color of the shape:
struct CircleView: View {
#State var isBlue = true
var body: some View {
Circle()
.fill(isBlue ? Color.blue : Color.red)
.tapAction {
withAnimation {
self.isBlue.toggle()
}
}
}
}