SwiftUI: Shadow glitch after context menu - swiftui

I have a rectangle with a shadow and a context menu. When I close this context menu the shadow of the rectangle appears with a delay (~0.5 seconds). Both the shadow of the complete rectangle as well the shadow of the inner elements. I'm not sure what I am doing wrong.
struct Playground: View {
var body: some View {
VStack(alignment: .leading, spacing: 4.0) {
Spacer()
HStack {
Spacer()
Image(systemName: "house")
.font(.system(size: 50))
Spacer()
}
Text("SwiftUI for iOS 14").fontWeight(.bold).foregroundColor(Color.white)
Text("20 Sections").font(.footnote).foregroundColor(Color.white)
}
.frame(width: 200, height: 300)
.padding(.all)
.background(Color.blue)
.cornerRadius(20.0)
.shadow(radius: 10)
.contentShape(RoundedRectangle(cornerRadius: 20))
.contextMenu(menuItems: {
Text("Menu Item 1")
Text("Menu Item 2")
Text("Menu Item 3")
})
}
}

I found a hacky way to mitigate the issue. This is using pure UIKit and Objective-C but it might be able to be done in SwiftUI too in one way or another.
This is a temporary direct-via-subview access to the view that animates, and I simply attach a shadow to it. This way the animating view will have a shadow and when the REAL shadow pops in, it does so below this shadow. The result looks fine.
-(void)contextMenuInteraction:(UIContextMenuInteraction *)interaction
willEndForConfiguration:(UIContextMenuConfiguration *)configuration
animator:(id<UIContextMenuInteractionAnimating>)animator {
// TODO: Find view dynamically /safer
UIView *platter = (UIView*)self.view.window.subviews[1].subviews[1].subviews[0];
platter.clipsToBounds = NO;
platter.layer.shadowOffset = CGSizeMake(0, 0);
platter.layer.shadowRadius = 5;
platter.layer.shadowOpacity = 0.5;
platter.layer.shadowColor = [UIColor blackColor].CGColor;
}
This works in iOS 13. Have not tried on iOS 14.

Related

Show SwiftUI Dialog box on top of UIKit View

Newbie at SwiftUI here. I am trying to show a dialog built in SwiftUI on top of an existing UIKit View. The idea is to be able to see the content of the UIKit view behind the SwiftUI dialog (like the default behaviour of an alert dialog box). But no what I try, I am unable to see the contents of the UIKit view. Is this even achievable?
I want an alert style dialog with background opacity adjusted somehow to see the contents of the UIKit view. Here is my output:
alert content hides the view behind it
Can somebody please point me in the right direction.
Here is my code sample:
The dialog in SwiftUI:
struct TestDialog: View {
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.black.opacity(0.5))
.frame(maxHeight: .infinity)
VStack(alignment: .center, spacing: 15) {
Text(.init("Some Text"))
.multilineTextAlignment(.center)
.padding()
Button(action: {}) {
Text("Button 1")
.padding(10)
}
Button(action: {}) {
Text("Button 2")
.padding(10)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.white))
.padding(40)
}
}
}
and the method called in my viewDidLoad():
func showTestDialog() {
let testView = TestDialog()
let testchildView = UIHostingController(rootView: testView)
addChild(testchildView)
let titleBarOffset: CGFloat = 11
testchildView.view.frame = view.frame.offsetBy(dx: 0, dy: -titleBarOffset)
view.addSubview(testchildView.view)
testchildView.didMove(toParent: self)
}
In order to see through the SwiftUI, you need to make sure it's host view has a transparent background:
testchildView.view.backgroundColor = .clear
This must be done at the host view level, since it is the parent/container.

SwiftUI: Spacers won't scale down because BG Image will not adjust to device size

I use a ZStack to display a fullscreen background image underneath the main UI. The main UI consists of a VStack with multiple views separated by flexible Spacers to scale down or up on different device sizes. Now I experience that the Spacers will not scale down on small devices because the background image on small devices remains bigger than the screen size and keeps the ZStack tall, see screenshot of the preview of iPhone 8. What am I doing wrong here?
Code:
import SwiftUI
struct TestView: View {
var headerView: some View {
VStack {
Text("HEADER").padding([.leading,.trailing], 100)
}
}
var middleView: some View {
HStack {
Text("MIDDLE").padding([.leading,.trailing], 100)
}
}
var bottomView: some View {
VStack {
Text("BOTTOM").padding([.leading,.trailing], 100)
}
}
var body: some View {
ZStack {
// Background Image
Image("BgImg")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
// Main UI
VStack(spacing: 0) {
headerView
.frame(height:222)
.background(Color.red)
Spacer()
middleView
.frame(height:155)
.background(Color.orange)
Spacer()
bottomView
.frame(height:288)
.background(Color.yellow)
}
.padding(.bottom, 32)
.padding(.top, 54)
// END Main UI
}
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
// END ZStack
}
}
Preview Screenshot:
See blue border is bigger than device
BG Image:
In described scenario you need to use .background instead of ZStack, such so main view form needed full-screen layout and image in background will not affect it.
So the layout should be like
VStack(spacing: 0) {
// content here
}
.padding(.bottom, 32)
.padding(.top, 54)
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
.background(
// image here
)

SwiftUI Image Opacity Animation affecting Other Views

I'm trying to build a list with progress bar and status icon. I want to animate the icon to indicate that some download activity is in progress. However, when I apply the animation, it also affects the progress bar and causes it to animate.
Here's the code for the list view:
List(syncProgres.downloadQueue) { mediaFile in
VStack(alignment: .leading) {
Text(mediaFile.path).font(.callout).lineLimit(2).truncationMode(.middle)
Spacer()
ProgressView(value: Double(mediaFile.downloadedBytes) / Double(mediaFile.downloadTotalBytes))
HStack {
Text("\(MiscUtils.toHumanReadable(bytes: mediaFile.downloadedBytes)) of \(MiscUtils.toHumanReadable(bytes: mediaFile.downloadTotalBytes))").font(.footnote)
Spacer()
Image(systemName: "arrow.down.doc").opacity(opacity)
.onAppear {
let baseAnimation = Animation.linear(duration: 1)
let repeated = baseAnimation.repeatForever(autoreverses: true)
withAnimation(repeated) {
opacity = 0.2
}
}
}
}.frame(height: 80, alignment: .center)
}
So swiftUI view modifiers work bottom to top, if the progress bar is getting the animation from the view below it add '.animation(.default)' below the view that you don't want affected.
List(syncProgres.downloadQueue) { mediaFile in
VStack(alignment: .leading) {
Text(mediaFile.path).font(.callout).lineLimit(2).truncationMode(.middle)
Spacer()
ProgressView(value: Double(mediaFile.downloadedBytes) / Double(mediaFile.downloadTotalBytes))
.animation(.default)
HStack {
Text("\(MiscUtils.toHumanReadable(bytes: mediaFile.downloadedBytes)) of \(MiscUtils.toHumanReadable(bytes: mediaFile.downloadTotalBytes))").font(.footnote)
Spacer()
Image(systemName: "arrow.down.doc").opacity(opacity)
.onAppear {
let baseAnimation = Animation.linear(duration: 1)
let repeated = baseAnimation.repeatForever(autoreverses: true)
withAnimation(repeated) {
opacity = 0.2
}
}
}
}.frame(height: 80, alignment: .center)
}

Detailed list view - top alignment question

Added on the 24th of July:
This line of code fixes the space in the detail view. However... in the list view the title has become a lot smaller too.
.navigationBarTitle(Text("Egg management"), displayMode: .inline)
Added on the 23th of July:
Thanks to the tips I made a lot of progress. Especially the tip to add borders does wonders. You see exactly what happens!
However, there seems to be a difference between the Xcode Preview canvas, the simulator and the physical device. Is this a bug because -after all- it is still beta? Or is there anything I can do?
As you can see in the images... only in the Xcode Preview canvas the view connects to the top of the screen.
I believe it has something to do with the tabbar. Since when I look at the Xcode Preview canvas with the tabbar... that space above is also there. Any idea how to get rid of that?
Original postings:
This is my code for a detailed list view:
import SwiftUI
struct ContentDetail : View {
#State var photo = true
var text = "Een kip ..."
var imageList = "Dag-3"
var day = "3.circle"
var date = "9 augustus 2019"
var imageDetail = "Day-3"
var weight = "35.48"
var body: some View {
VStack (alignment: .center, spacing: 10) {
Text(date)
.font(.title)
.fontWeight(.medium)
ZStack (alignment: .topLeading){
Image(photo ? imageDetail : imageList)
.resizable()
.aspectRatio(contentMode: .fit)
.background(Color.black)
.padding(.trailing, 0)
.tapAction {
self.photo.toggle() }
HStack {
Image(systemName: day)
.resizable()
.padding(.leading, 10)
.padding(.top, 10)
.frame(width: 40, height: 32)
.foregroundColor(.white)
Spacer()
Image(systemName: photo ? "photo" : "pencil.circle")
.resizable()
.padding(.trailing, 10)
.padding(.top, 10)
.frame(width: 32, height: 32)
.foregroundColor(.white)
}
}
Text(text)
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
} .padding(20)
}
}
#if DEBUG
struct ContentDetail_Previews : PreviewProvider {
static var previews: some View {
ContentDetail()
}
}
#endif
Also included is the preview canvas. What I don't get is how I can make sure the text and photo are aligned to the top (instead of the middle). I tried with Spacers, padding etc.
I must be overseeing something small I guess... but. Can somebody point me in the right direction? Thanks.
Added:
After both answers I added a Spacer() after the last text. In Xcode in the preview canvas everything looks okay now. But on my connected iPhone 7 Plus there are some problems: the view is not aligned to the top, and the image is cropped (icon on the right is gone; white banding to the right).
Adding a Spacer() after the last text shifts everything to the top. Tested on iPhone Xr simulator (not preview).
...
Text(text)
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
Spacer()
}
To remove the space at the top:
VStack {
...
}
.padding(20)
.navigationBarTitle("TITLE", displayMode: .inline)
Think in terms of what a Spacer() does. It "moves" the views as far apart as it can - at least, without a specific space.
So you have this:
VStack {
Text
ZStack {
Image
HStack {
Image
Spacer()
Image
}
}
Text
}
All told, going from inner to outer, you have a horizontal stack of two images placed as far apart (the spacer is between them) inside of a "Z axis" stack that places an image on top of them, inside of a vertical stack that has some text above it.
So if you want to move everything in that vertical stack to the top, you simply need to add one last spacer:
VStack {
Text
ZStack {
Image
HStack {
Image
Spacer()
Image
}
}
Text
Spacer() // <-- ADD THIS
}
Last note: Don't be afraid to adding additional "stacks" to your view. In terms of memory footprint, it's really just a single view with no performance hit.
EDIT: I took your original view and changed everything to placeholders...
var body: some View {
VStack (alignment: .center, spacing: 10) {
Text("Text #1")
.font(.title)
.fontWeight(.medium)
ZStack (alignment: .topLeading) {
Text( "Image #1")
HStack {
Text("Image #2")
Spacer()
Text("Image #3")
}
}
Text("Text #2")
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
} .padding(20)
}
As expected, everything is vertically centered. Adding a Spacer() below "Text #2" throws everything to the top. A couple of thoughts:
Starting there, and add in your Image views one by one. Add in the modifiers like that also.
I don't have the specific images you are rendering, so maybe put a noticeable background color on various things (orange is my personal favorite) and see if the top Image is actually on top but the image makes it appear as though it isn't. A border would work pretty well too.

SwiftUI: Center Content in ScrollView

I'm trying to center a bunch of views in a VStack within a ScrollView in SwiftUI. To simplify things, I'm just trying to get it to work with a single Text view. Here's what I've come up with so far:
var body: some View {
ScrollView(alwaysBounceVertical: true){
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
Spacer()
} //HStack
.background(Color.green)
} //ScrollView
.background(Color.gray)
}
This results in this:
I want the text to be in the middle like this:
So the HStack should be full-width and the Text should be centered within it. It seems like this should be easy, but I don't get what I'm doing wrong. :)
Using GeometryReader, you can get information about the size of the containing view and use that to size your view.
var body: some View {
GeometryReader { geometry in <--- Added
ScrollView(alwaysBounceVertical: true){
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
Spacer()
} //HStack
.frame(width: geometry.size.width) <--- Added
.background(Color.green)
} //ScrollView
.background(Color.gray)
}
}
edit: after looking into this more, it seems that part of the problem is that you are using a ScrollView. If you remove that parent, the spacers in the HStack will automatically cause stretching to fill the view. I'm guessing the automatic stretching doesn't happen in ScrollViews because there's no finite limit to how big it can be, how much would it stretch? (because a ScrollView can scroll in any direction)
This seems to be a bug in Xcode 11.0 beta, ScrollView content wouldn't fill the scroll view. If you replace the ScrollView with a List it will work as expected. But if you have to use a scroll view, one workaround is to fix the scroll view's content width.
So your code will look something like this:
ScrollView(alwaysBounceVertical: true) {
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
Spacer()
} // HStack
.frame(width: UIScreen.main.bounds.width) // set a fixed width
.background(Color.green)
} // ScrollView
.background(Color.gray)
Result:
You should use the modifier frame and set its maxWidth: .infinity.
So it tells its parent: "the wider, the better" :)
var body: some View {
ScrollView(.vertical, showsIndicators: true){
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
.frame(maxWidth: .infinity) // <- this
Spacer()
} //HStack
.background(Color.green)
} //ScrollView
.background(Color.gray)
}
And this works regardless its parent.
Scrollview or whatever View it's set in.
Paul is doing a great job clarifying it to all of us here:
https://www.hackingwithswift.com/quick-start/swiftui/how-to-give-a-view-a-custom-frame
Answer compatible with Xcode 12.1 (12A7403)
I hope this helps 👍
dsa