Dismiss button (X) on an image - top right alignment HOW? - swiftui

What is an effective & effecient way to get the Dismiss button (X) into the top right corner?
I'm struggling with container alignment... can't say I GROK it.
Needless to say ... this ain't it!
var body: some View {
ZStack {
Image("Biz-card_2020")
.resizable()
.edgesIgnoringSafeArea(.all)
HStack(alignment: .top) {
VStack() {
Spacer(minLength: 5) // vertical space
HStack() {
Spacer()
// close Welcome page (X) button
Button(action: {
//print(" - Button to dismiss page \(self.isPresented)")
self.isPresented = false // dismiss the Welcome view
//print(" - after action Button to dismiss Welcome page \(self.isPresented)")
}, label: {
Image(systemName: "xmark.circle" )
.scaledFont(name: "Georgia", size: Const.titleText)
.minimumScaleFactor(0.3)
.accentColor(.white)
.padding(10)
})
}
Spacer()
}
}
}
}

You need to remove Spacer(minLength: 5) and replace it with padding for HStack.
Spacer(minLength: 5) doesn't mean its length will be exactly 5 (only that the minimum length will be 5).
You may also want to extract close button to another function for clarity.
Try the following:
struct ContentView: View {
...
var body: some View {
ZStack {
Image("Biz-card_2020")
.resizable()
.edgesIgnoringSafeArea(.all)
closeButton
}
}
var closeButton: some View {
VStack {
HStack {
Spacer()
Button(action: {
...
}) {
Image(systemName: "xmark.circle")
.padding(10)
}
}
.padding(.top, 5)
Spacer()
}
}
}

ZStack can be configured using .topTrailing etc. You should use those alignments to configure your view. Using something like Spacer() is going to cause the rest of your views to get pushed down.
ZStack(alignment: .topTrailing) {
// your code here
}
If you need to move your code a bit more, use either padding, or offset modifiers.

Related

SwiftUI How to make a slide in view that pushes another view away while keeping it the same size

Before Slide
After Slide
struct SlideView: View {
#State var isClicked = false
var body: some View {
HStack{
Rectangle()
.fill(.gray)
.ignoresSafeArea()
.frame(width: isClicked ? UIScreen.main.bounds.width * 0.75 : 0)
VStack{
Text("This gets squished")
HStack{
Button{
withAnimation(.spring()) {
isClicked.toggle()
}
} label: {
Image(systemName: "menucard.fill")
.padding(.leading)
}
Spacer()
}
Spacer()
}
}
}
}
So I have this view that kind of "slides in" and it's pretty close to what I want but not exactly, the view that slides in ends up squishing the other view and compressing it to be about 25% of it's size. Of course this makes sense because it's all in a HStack and I end up taking 75% of the HStack's space but I was wondering if anybody had an idea of how I could do something like this but without squishing the second view that gets pushed away? So basically keep it the same size and just have the first 1/4 of the view visible and the other 3/4 just be gone I guess.
The problem is the view that gets squished has no explicit width. Understand how views get sized in SwiftUI. The parent view proposes a size, and the child view responds with the size it wants. Before you click the button, the parent view (the HStack) is proposing 100% to the view. That view says that I can do 100%, so no squishing. However, when you make the other view 75% of the parent, the parent can only offer 25% to the first view. The view responds, I can fit if I squish, so it does. See Laying out a simple view.
To fix it, you simply need to explicitly set the width of the first view to 100%, and then it will get pushed over like you were expecting without compression.
struct SlideInView: View {
#State var isClicked = false
var body: some View {
// I swapped out UIScreen.main.bounds for a GeometryReader
// UIScreen.main.bounds.width always returns the width of the screen
// even if the parent view does not have that much space to offer, and
// can lead to some interesting results.
GeometryReader { geometry in
HStack{
Rectangle()
.fill(.gray)
.ignoresSafeArea()
.frame(width: isClicked ? geometry.size.width * 0.75 : 0)
VStack{
Text("This gets squished")
HStack{
Button{
withAnimation(.spring()) {
isClicked.toggle()
}
} label: {
Image(systemName: "menucard.fill")
.padding(.leading)
}
Spacer()
}
Spacer()
}
.frame(width: geometry.size.width) // Explicitly set width here
}
}
}
}
Personally, I think it would make more sense to just use .offset, and put them in a ZStack:
ZStack {
VStack {
HStack {
Spacer()
Text("yellow")
Spacer()
}
Spacer()
}
.background(.yellow)
VStack {
Text("red")
HStack {
Button{
withAnimation(.spring()) {
isClicked.toggle()
}
} label: {
Image(systemName: "menucard.fill")
.padding(.leading)
}
Spacer()
}
Spacer()
}
.background(.red)
.offset(.init(width: isClicked ? UIScreen.main.bounds.width * 0.75 : 0, height: 0))
}
And you can add another .offset modifier to the yellow one to make it looks slidein:
VStack {
//...
}
.background(.yellow)
.offset(.init(width: isClicked ? 0 : -UIScreen.main.bounds.width * 0.75, height: 0))

How can implement a navigationLink inside of a LazyVGrid and ForEach statement?

How can I implement play buttons that navigate to different views in the corner of each music category item? Here is an image of what I am looking for:
Here is my code:
struct ScrollCategories: View {
var body: some View {
ZStack {
ScrollView {
LazyVGrid (columns: [GridItem(.fixed(200)), GridItem(.fixed(200))]) {
ForEach(musics) { sub in
ZStack {
VStack(alignment: .leading) {
//Hidden Code
} // END OF VSTACK
NavigationLink {
// Naviagte to a different view!
Onboarding()
} label: {
Image(systemName: "play.circle")
.font(.system(size: 30))
.padding(.leading, -72.0)
.padding(.bottom, 130.0)
}
} // END OF ZSTACK
}// END OF FOR EACH
} // END OF GRID ITEM
} // END OF SCROLL VIEW
} //END OF ZSTACK
}
}
As you can see, I have a navigation link, yet it does not show in the preview or simulator.
Here is an image of what the preview looks like:
Your navigation link is there. You just use padding too high that it was out of the frame. You can use ZStack with alignment to put on the left-top corner and add padding to make it nice.
ZStack(alignment: .topLeading) { <- positioning for each View
VStack(alignment: .leading) {
//Hidden Code
} // END OF VSTACK// END OF VSTACk
NavigationLink {
// Navigate to a different view!
Onboarding()
} label: {
Image(systemName: "play.circle")
.font(.system(size: 30))
.padding( {{ put your desire padding }})
}
} // END OF ZSTACK

Why do the views extend wider than the screen?

Edit: Substitute your "system name:" of choice. "pencil.circle" works fine. "edit" is not a valid SF Symbol.
(I've simplified my code so you can cut and paste. That's why you see .frame, resizable, etc. where much simpler code might your first instinct.)
I have created a view which is a vertical list of row items (table view).
Each row item has a horizontal view with two images inside it.
The images take up too much space and do not fit correctly on the screen:
import SwiftUI
#main
struct StackOverflowDemoApp: App {
var body: some Scene {
WindowGroup {
TandemView()
}
}
}
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(height: 80)
.aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(width: 200, height: 80)
}
.padding()
.fixedSize()
}
}
struct TandemView_Previews: PreviewProvider {
static var previews: some View {
TandemView()
}
}
The above is the closest I can get to the desired layout (it just needs to fit horizontally). I experimented with GeometryReader but that did not produce desired results.
Here are some things I tried:
The code as provided
NoConstraintsOnPencilOrHStack
NoConstraintsOnTandemView
NoConstraintsOnImageInPaddedViewButWithFrameConstraint
I am trying to get a row view which consists of two Images (my actual source consists of UIImage objects) that fits within the width of the screen.
Edit:
After Accepting cedricbahirwe's spot-on response, I was able to simplify the code further. New results:
I added at the top level
TandemView()
.padding(.horizontal)
I removed:
// Spacer()
at the end of PaddedImageView
updated TandemView -- changed both frames and removed 3 lines:
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(width: 80, height: 80)
// .aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(height: 80)
}
// .padding()
// .fixedSize()
}
}
This is happening because of the layout of PaddedImageView View, you can actually remove the Spacer since it is not needed there.
So change
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
to
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
}
}
Note:
SwiftUI Engine infers the layout of your view from the implementation of the body property. It's recommended to have one Parent View inside the body property.

How to position text and a button in this manner?

This is an example of what I am trying to do.Link to image of design to implement. I am unsure how to position a button and text at the top of the screen in this manner coding in swiftui. Alternatively I thought I could use the navigation bar inline and customise that but I am unsure.
var body: some View {
VStack (alignment: .trailing) {
HStack(spacing:10) {
Button(action: {
}) {
Image(systemName: "line.horizontal.3")
.font(.headline)
}
Text("WeCollab")
.foregroundColor(.white)
.font(.title)
//padding(.leading,40)
Spacer()
}
.padding(.top,UIApplication.shared.windows.first?.safeAreaInsets.top)
.background(customPurpleColour)
Spacer()
}
.edgesIgnoringSafeArea(.top)
}
}
Seems like you're on the right track. In order to keep the title centered, it seemed easier to make a ZStack. The menu button gets it's own .leading-aligned VStack and then the title goes on top of that.
The edgesIgnoringSafeArea and padding can be simplified so that you don't have to use the screen size safe areas.
struct ContentView: View {
var body: some View {
VStack {
ZStack {
VStack {
Button(action: { }) {
Image(systemName: "line.horizontal.3")
.font(.headline)
}.padding(.leading)
}.frame(maxWidth: .infinity, alignment: .leading)
Text("WeCollab")
.foregroundColor(.white)
.font(.title)
}
.background(Color.purple.edgesIgnoringSafeArea(.top))
Spacer() //other content goes here
}
}
}

SwiftUI 2.0: Close button is not dismissing the view - how do I get the Close button to return to the previous view?

I have tried to use Buttons and Navigation Links from various examples when researched on this channel and on the net. The NavigationLink would be ok, except that the NavigationView is pushing everything down in my view.
I have a view that contains an image and a text like this: ( x Close) but when I use the code below, the Close button is not doing anything.
In ContentView() I have a (?) button that takes me from WalkthroughView(), then to the PageTabView, then to this view, TabDetailsView:
ContentView():
ZStack {
NavigationView {
VStack {
Text("Hello World")
.padding()
.font(.title)
.background(Color.red)
.foregroundColor(.white)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
showOnBoarding = true
}
} label: {
Image(systemName: "questionmark.circle.fill")
}
}
}
}
.accentColor(.red)
.disabled(showOnBoarding)
.blur(radius: showOnBoarding ? 3.0 : 0)
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
}
.onAppear {
if !isWalkthroughViewShowing {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
showOnBoarding.toggle()
isWalkthroughViewShowing = true
}
}
}
}
WalkthroughView():
var body: some View {
ZStack {
GradientView()
VStack {
PageTabView(selection: $selection)
// shows Previous/Next buttons only
ButtonsView(selection: $selection)
}
}
.transition(.move(edge: .bottom))
}
PageTabView():
var body: some View {
TabView(selection: $selection) {
ForEach(tabs.indices, id: \.self) { index in
TabDetailsView(index: index)
}
}
.tabViewStyle(PageTabViewStyle())
}
below, is the TabDetailsView():
At the top of the view is this Close button, when pressed, should send me back to ContentView, but nothing is happening.
struct TabDetailsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let index: Int
then, inside the body:
VStack(alignment: .leading) {
Spacer()
VStack(alignment: .leading) {
// Button to close each walkthrough page...
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}
.padding(.leading)
.font(.title2)
.accentColor(.orange)
Spacer()
VStack {
Spacer()
Image(tabs[index].image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 415)
.padding(.leading, 10)
Text(tabs[index].title)
.font(.title)
.bold()
Text(tabs[index].text)
.padding()
Spacer()
}
.foregroundColor(.white)
}
}
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
Inserting view like above is not a presentation in standard meaning, that's why provided code does not work.
As this view is shown via showOnBoarding it should be hidden also via showOnBoarding, thus the solution is to pass binding to this state into view where it will be toggled back.
Due to deep hierarchy the most appropriate way is to use custom environment value. For simplicity let's use ResetDefault from https://stackoverflow.com/a/61847419/12299030 (you can rename it in your code)
So required modifications:
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
.environment(\.resetDefault, $showOnBoarding)
}
and in child view
struct TabDetailsView: View {
#Environment(\.resetDefault) var showOnBoarding
// .. other code
Button(action: {
self.showOnBoarding.wrappedValue.toggle()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}