Image full screen width in SwiftUI - swift

I added an image to my body in a SwiftUI application and want to have that image cover the full width of the device, but not go over it.
In body, I return the image object:
var body: some View {
Image("page-under-construction")
}
and the image shows up, however, it's too big:
I tried setting the frame: that affects the highlighted boundaries, but the image does not resize.
In combination, I played around with .aspectRatio(contentMode:), which did not seem to have any effect on the layout.
How can I have the image be effectively 100% of the screen width?

The reason .aspectRatio(contentMode:) had no effect on the layout is because you did not make the image resizable with resizeable().
Doing
var body: some View {
Image("page-under-construction")
.resizable()
.aspectRatio(contentMode: .fill)
}
will cause the image to be the width of the screen, but the image's aspect ratio will not be maintained. To maintain the aspect ratio, do
var body: some View {
Image("page-under-construction")
.resizable()
.aspectRatio(UIImage(named: "page-under-construction")!.size, contentMode: .fill)
}
This utilizes the .aspectRatio(aspectRatio: CGSize, contentMode: ContentMode) version of the method your original question discussed with a dummy UIImage to access the Image's original aspect ratio.
Note: The explicitly unwrapped optional (!) should not be a problem unless you are unsure if the image name is a valid one from your Assets folder. See this post for a comprehensive overview on Swift optionals.

Here is how you add a background image and make it fill the full screen in SwiftUI
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.top)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Did you try resizable modifier?
struct ImageView: View {
var body: some View {
Image("turtlerock")
.resizable()
.aspectRatio(contentMode: .fit)
}}
Note - There are 2 content modes: .fit and .fill

Related

Fullscreen LazyHStack for Images (Landscape)

This is my first question ever on StackOverflow so please bear with me!
How do I get a fullscreen ScrollView with LazyHStack while ignoring safe edges with the each image taking up 100% of the device screen?
I was able to get something like this that worked using TabView but since there is no "LazyTabView" it was not an efficient way to do things.. I have also tried multiple other methods utilizing Geometry Reader and frame but to no avail. Also note that I am strictly working in landscape orientation so how it looks in portrait does not matter to me.
Here is the code I'm currently using:
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack() {
Image("test")
.resizable()
.scaledToFill()
Image("test")
.resizable()
.scaledToFill()
Image("test")
.resizable()
.scaledToFill()
Image("test")
.resizable()
.scaledToFill()
}
}
.ignoresSafeArea()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have also attached an image to show the results I am getting. I am hoping I can have the image take up 100% of the screen real estate without showing the edge of the next image. As you can see the image does not fill the entire screen and instead shows the edge of the next photo. I hope this makes sense!
screenshot of code with preview
This code worked for me:
struct ContentView: View {
var body: some View {
GeometryReader { g in
ScrollView(.horizontal, showsIndicators: false) {
HStack() {
Image("test")
.resizable()
.ignoresSafeArea()
.frame(width: g.size.width + CGFloat(g.safeAreaInsets.leading) + CGFloat(g.safeAreaInsets.trailing), height: g.size.height + CGFloat(g.safeAreaInsets.bottom))
.offset(x: -CGFloat(g.safeAreaInsets.leading))
}
}
}
}
}
It will make the image take up the whole screen, ignoring the safe area. You may have to take the code out of the LazyHStack and into a HStack instead, however. Unless you have a large number of images, you do not need to use LazyHStack.

Changing the spacing of the underline of a UIButton

How can I change the spacing between a UIButton and its underline? And also try to make the underline bolder. Any help is appreciated.
My UIButton:
enter image description here
The ideal UIButton:
enter image description here
To get such a button, you can split it int 2 elements - the text (not underlined) and a rectangle (as the line).
SwiftUI
In SwiftUI you can achieve your desired design with a Stack within the button with a text and a rectangle in it
Button(action: {
self.anyFancyFunction()
}) {
VStack(){
Text("Button")
Rectangle().frame(height: 5)
}
.foregroundColor(.black)
}
And it would look like:
In this example you can adjust the spacing by adding a (positive or negative) offset. You can add a corner radius to the bar and ich you wanted to have it rounded on the edges, you also can use Capsule() instead of Rectangle()
UPDATE:
UIKit
Regarding UIKit, I think a possible solution could be to create a view with 2 subviews (1. text, 2. bar underneath the text) and then you put a clear button with the same size over it on the same position.
I'll try to add an UIKit example in the afternoon.
Best,
Sebastian
In SwiftUI, there is a View called Rectangle that is perfectly matched for this. You can add it below any view by embedding them into a simple VStack.
Here is the code:
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
}) {
VStack(spacing: 15){
Text("帖子")
.font(.system(size: 30))
Rectangle()
}
.frame(width: 70, height: 60, alignment: .center)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is the result:

Arrange custom views in SwiftUI without space or overlap

I'm trying to build a UI (SwiftUI / iOS) out of a number of custom views.
All those custom views have a defined aspect ratio or ratio for their frame.
Here's a simplified version of such a custom view:
struct TestView: View {
var body: some View {
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.blue)
.frame(height: geometry.size.width / 3)
}
}
}
My ContentView currently looks like that:
struct TestContentView: View {
var body: some View {
GeometryReader {geomerty in
VStack {
TestView()
TestView()
}
}
}
}
I would like to have the two rectangles to be positioned right below each other (at the top of the screen). So without any space between them. So a bit like an old-fashioned UITableView with only to rows.
But whatever I try, I only get one of two results:
They are equally spread out over the screen (vertically)
They overlap (= the view on the top only gets a vertical size of 20
The only solution I've found so far is to define the frame size of the sub-views also in the TestContentView(). But that seems to be quite un-SwiftUI.
Thanks!
Remove the GeometryReader from your content view, since it isn't doing anything
You said that your TestView has a defined aspect ratio, but, in fact, it doesn't -- it just has a defined width. If you do define an aspect ratio, it starts working as expected:
struct TestView: View {
var body: some View {
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.blue)
.aspectRatio(3, contentMode: .fit)
}
}
struct TestContentView: View {
var body: some View {
VStack(spacing: 0) {
TestView()
TestView()
Spacer()
}
}
}

Clipped and Scaled To Fill SwiftUI Image Blocks NavigationLink

When there is a NavigationLink in a container with an Image (that is resizable, scaled to fill, and clipped to a smaller frame), the NavigationLink cannot be pressed. I'm assuming that this has to do with the parts of the Image that have been "clipped off" still actually present and blocking the NavigationLink.
Here is a short example to replicate the behavior:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
VStack {
NavigationLink(destination: Text("Hello, world!")) {
Text("Press me")
}
}
Image("background")
.resizable()
.scaledToFill()
.frame(height: 60)
.clipped()
}
}
}
}
"background" can be any sort of picture from the assets folder.
I have tried to mess around with the zIndexes; that didn't work.
There was one hack that worked: I used a UIImage, cropping it to the aspect ratio of Image I wanted by converting it to a CGImage and back into a UIImage. After doing that, I could press on the NavigationLink again but it was obvious from my phone lagging that it was too expensive. I tried to work around this by saving the cropped image to the documents directory and then whenever the aspect ratio wasn't similar enough I would recrop, save, and reload the image, but this still took a toll on the performance of my project.
Please offer some advice on how I should handle this situation. Thanks in advance for any help.
Here is alternate to zIndex (if other active elements are present in view as well) - disable user interaction with background image
Image("background")
.resizable()
.scaledToFill()
.frame(height: 60)
.clipped()
.allowsHitTesting(false) // << here !!
//.zIndex(-1) // << also force put below siblings
Set .zIndex(1.0) to VStack of NavigationLink.
Tested : XCode 12.2, iOS 14.1
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
VStack {
NavigationLink(destination: Text("Hello, world!")) {
Text("Press me")
}
}
.zIndex(1.0) //<--- here
Image("ivana-cajina-_7LbC5J-jw4-unsplash")
.resizable()
.scaledToFill()
.frame(height: 60)
.clipped()
}
}
}
}
Here is another alternative.
public var body: some View {
ZStack {
image
.resizable()
.scaledToFill()
// > etc..
.allowsHitTesting(false)
}
.clipped()
}

SwiftUI Zstack – Make element ignore safe area and another one don't

I have a Zstack like this:
ZStack {
Image("beach")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
VStack {
// with a lot of stuff
}
}
I want the image to ignore the safe area, but the Vstack must respect the safe area.
The image is a background image that should cover all the area.
This code I have is making the VStack full screen too and I don't want that.
Why everything is not respecting the safe area is a mystery, because the respective modifier is applied to the image only.
Put your image in the .background:
struct ContentView: View {
var body: some View {
VStack {
// lots of stuff
Color.red
Color.blue
}
.background(
Image("beach")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
)
}
}
The reason why it doesn't work with your example is that you are using scaledToFill() modifier on image, and here it matters in this particular case.
First you declared ZStack which is itself doesn't ignore safe area. After you put Image and resizable. What resizable does, is it stretches the image to fit its view, in your case it is the ZStack.
Let's see what we have until now.We have an image which stretches till the safe area.
ZStack {
Image("Breakfast").resizable()
}
So from now on you put edgesIgnoringSafeArea on Image, which lets the image cover all the area(ignore safe area).
Now you have Zstack which respects safe area, and Image which ignores safe area. This let you put VStack in ZStack and add staff inside it. VStack will fill its parent view, which is ZStack, so it too will respect safe area(See code and image below).
ZStack {
Image("Breakfast").resizable().edgesIgnoringSafeArea(.all)
VStack {
Text("hello")
Spacer()
}
}
And here at last you add .scaledToFill() modifier, which stretches the image to contain all the area, and by doing this it makes ZStack view to become the hole area, as fitting view's (ZStack, HStack, VStack) calculates its size based on its content.
Useful link:
Fitting and filling views in SwiftUI
How to resize a SwiftUI Image and keep its aspect ratio
Another way that worked for me is:
struct ContentView: View {
var body: some View {
ZStack {
Color.clear
.background(
Image("beach")
.resizable()
.ignoresSafeArea()
.scaledToFill()
)
VStack {
// lots of stuff
}
}
}
}
Note: In my case, when trying the other solution of putting .background on VStack the image did not fill the entire screen, instead it shrank to fit the size of what was in the VStack.