I need that red rectangle should be visible until reach to the leading or trailing edge of the device.
So the problem is scrollview cliping red boxes (hiding) as move beyond the scrollview container size.
code:
struct test: View {
var body: some View {
ScrollView(.horizontal,showsIndicators:false) {
LazyHStack{
ForEach(0...4,id:\.self){i in
Rectangle()
.fill(Color.red)
.frame(width: 60, height: 50, alignment: .center)
}
}.padding(.horizontal, 4)
}.background(Color.yellow)
.frame(width: 68, height: 60, alignment: .trailing)
}
}
Results:
Expected:(I also produce this result by setting ScrollView full width and adding padding (.padding(.horizontal, UIScreen.main.bounds.width/2 )) to LazyHStack)
But it is a hack by adding space at start and end, and problem remain unresolved, that is clipping of ScrollView content
I'm not exactly sure what the problem is, but you probably want the padding to be on the Text and not the ScrollView. Correct me if this isn't what you are looking for.
struct test: View {
var body: some View {
ScrollView(.horizontal) {
Text("Hello, World!")
.frame(width: UIScreen.main.bounds.width, alignment: .leading)
.padding(.horizontal, 40)
}
.background(Color.yellow)
}
}
Result:
It seems to be working for me when I remove the line: .frame(width: UIScreen.main.bounds.width, alignment: .leading).
On an iPhone XR, I'm seeing a horizontal ScrollView with a yellow background starting at 40 offset from leading and ending at 40 offset from trailing. Is this what you're trying to achieve?
Also, I'm pretty sure UIScreen.main.bounds.width is going to return the width of the device, which will be a problem if you want your text to take up 80 pixels less than that value (since your ScrollView has 40 padding either side).
if I understand correct from your answers, this is the result you want:
struct test: View {
var body: some View {
GeometryReader { geo in
VStack {
Spacer()
HStack {
ScrollView(.horizontal,showsIndicators:false) {
LazyHStack{
ForEach(0...4,id:\.self){i in
Rectangle()
.fill(Color.red)
.frame(width: 60, height: 50, alignment: .center)
}
}
.padding(.horizontal,40)
}
.background(Color.yellow)
.frame(width: geo.size.width, height: 60, alignment: .center)
}
Spacer()
}
}
}
}
Result:
Related
In the wonderful world of SwiftUI, I have a View that I use as a cell. I intend to reproduce the Layout of a previous application of mine, with Autolayout, not SwiftUI, in which the background image filled the entire cell, adjusting to the width and losing pieces of image above and below.
In my new app, the code in SwiftUI is the following:
struct CharacterRow2: View {
var character: Character
var body: some View {
Text(character.name)
.font(Font.custom(avengeanceHeroicAvengerNormal, size: 30))
.foregroundColor(.white)
.baselineOffset(-10)
.shadow(color: .black, radius: 1, x: -1, y: 1)
.frame(width: UIScreen.main.bounds.width, height: 140)
.background {
WebImage(url: extractImage(data: character.thumbnail))
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 150)
}
}
}
With this code, my app looks like this:
I tried to add scaledToFill():
WebImage(url: extractImage(data: character.thumbnail))
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 150)
But this is the result:
I'm stuck...
Thank you in advance!
In this case, you are simply using too many frames. And using them incorrectly. You should avoid using UIScreen.main.bounds in SwiftUI, especially in something like a view cell. By doing this, the cell will not behave properly with other views, and can cause UI issues that would be difficult to trace.
The simplest way to get the behavior you want is this:
Text(character.name)
.font(.largeTitle)
.foregroundColor(.white)
.baselineOffset(-10)
.shadow(color: .black, radius: 1, x: -1, y: 1)
.frame(height: 140)
.frame(maxWidth: .infinity) // use .infinity for max width to make it
// as large as the space offered by the
// parent view
.background {
WebImage(url: extractImage(data: character.thumbnail))
.resizable()
// .frame(width: UIScreen.main.bounds.width, height: 150) <-- Remove this frame altogether
.scaledToFill()
}
.clipped // This keeps the image from being larger than the frame
This will size to be as wide as the parent view allows it to be. Leaving the UIScreen.main.bounds.width could cause it to be larger than the parent view and cause partial eclipsing.
In your last example the images are overlaying each other. This is due to calling scaleToFill(). The images are now ignoring their frame boundaries regarding the height. Adding .clipped solves the problem.
struct CharacterRow2: View {
var character: String
var body: some View {
Text(character)
.font(.largeTitle)
.foregroundColor(.white)
.baselineOffset(-10)
.shadow(color: .black, radius: 1, x: -1, y: 1)
.frame(width: UIScreen.main.bounds.width, height: 140)
.background {
Image(character)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 150)
.clipped() // <-- Add this
}
}
}
It seems this will work only with a ForEach inside a ScrollView. Using a List seems to breack the vertical frame boundary.
struct ContentView: View{
let content = ["1.jpg", "2.jpg", "3.jpg" ]
var body: some View{
//These look really weird
// List(content, id: \.self){ name in
// CharacterRow2(character: name)
// }
// List{
// VStack{
// ForEach(content, id: \.self){ name in
// CharacterRow2(character: name)
// }
// }
// }
//working
ScrollView{
VStack{
ForEach(content, id: \.self){ name in
CharacterRow2(character: name)
.padding()
}
}
}
}
}
Result:
I created very simple views with SwiftUI including ZStack.
struct ContentView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Text("aaa")
.frame(width: 50, height: 50)
.font(.system(size: 20))
}
.frame(width: 142.0, height: 142.0)
.background(.pink)
}
}
I expected that the Text("aaa") would appear in the top leading of pink square.
But the result was this.
More strangely, it works well if I add 'Color.clear' to ZStack like this.
struct ContentView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Color.clear
Text("aaa")
.frame(width: 50, height: 50)
.font(.system(size: 20))
}
.frame(width: 142.0, height: 142.0)
.background(.pink)
}
}
And the result it this.
I cannot understand this situation. SwiftUI is totally crazy.
Does anybody know about this?
The elements of the ZStack are laid out and aligned based on the size of the largest child, not the frame of the ZStack itself.
You should think of the "content area" of the stack being seperate to the frame of the stack.
When you add a Color.clear, its default frame is unbounded (the height and width are infinity), so when it is added to the ZStack, it will grow the "content area" to the maximum possible size.
This makes the content area of the stack the same size as the ZStacks frame.
You can achieve the same result (in a clearer way) by using a Spacer() with explicit infinite bounds.
This will ensure the children always fill the same available to them.
struct ContentView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Spacer()
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text("aaa")
.frame(width: 50, height: 50)
.font(.system(size: 20))
}
.frame(width: 142.0, height: 142.0)
.background(.pink)
}
}
I'm trying to achieve something that is quite easy in UIKit - one view that is always in in the center (image) and the second view (text) is on top of it with some spacing between two views. I tried many different approaches (mainly using alignmentGuide but nothing worked as I'd like).
code:
ZStack {
Rectangle()
.foregroundColor(Color.red)
VStack {
Text("Test")
.padding([.bottom], 20) // I want to define spacing between two views
Image(systemName: "circle")
.resizable()
.alignmentGuide(VerticalAlignment.center, computeValue: { value in
value[VerticalAlignment.center] + value.height
})
.frame(width: 20, height: 20)
}
}
.frame(width: 100, height: 100)
result:
As you can see image is not perfectly centered and it actually depends on the padding value of the Text. Is there any way to force vertical and horizontal alignment to be centered in the superview and layout second view without affecting centered view?
I think the “correct” way to do this is to define a custom alignment:
extension VerticalAlignment {
static var custom: VerticalAlignment {
struct CustomAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[VerticalAlignment.center]
}
}
return .init(CustomAlignment.self)
}
}
Then, tell your ZStack to use the custom alignment, and use alignmentGuide to explicitly set the custom alignment on your circle:
PlaygroundPage.current.setLiveView(
ZStack(alignment: .init(horizontal: .center, vertical: .custom)) {
Color.white
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
VStack {
Text("Test")
Circle()
.stroke(Color.white)
.frame(width: 20, height: 20)
.alignmentGuide(.custom, computeValue: { $0.height / 2 })
}
}
.frame(width: 300, height: 300)
)
Result:
You can center the Image by moving it to ZStack. Then apply .alignmentGuide to the Text:
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.foregroundColor(Color.red)
Text("Test")
.alignmentGuide(VerticalAlignment.center) { $0[.bottom] + $0.height }
Image(systemName: "circle")
.resizable()
.frame(width: 20, height: 20)
}
.frame(width: 100, height: 100)
}
}
Note that as you specify the width/height of the Image explicitly:
Image(systemName: "circle")
.resizable()
.frame(width: 20, height: 20)
you can specify the .alignmentGuide explicitly as well:
.alignmentGuide(VerticalAlignment.center) { $0[.bottom] + 50 }
Here is possible alternate, using automatic space consuming feature
Tested with Xcode 12 / iOS 14
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.foregroundColor(Color.red)
VStack(spacing: 0) {
Color.clear
.overlay(
Text("Test").padding([.bottom], 10),
alignment: .bottom)
Image(systemName: "circle")
.resizable()
.frame(width: 20, height: 20)
Color.clear
}
}
.frame(width: 100, height: 100)
}
}
Note: before I used Spacer() for such purpose but with Swift 2.0 it appears spacer becomes always just a spacer, ie. nothing can be attached to it - maybe bug.
See the following sample code:
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(0 ..< 12) { index in
Rectangle()
.foregroundColor(.white)
.frame(width: 100, height: 100, alignment: .center)
.shadow(radius: 20)
}
}
}
This sample code will result in a horizontal ScrollView
with Rectangle as elements.
However, as it seems the ScrollView actually clips it's content
so any shadow on the top and bottom will be clipped.
Is there any way to disable automatic clipping by the scrollview
or another way to achieve this?
Shadow won't affect the frame of the Rectangle. So, try to add padding to the Rectangle, so the Shadow will be still visible, by increasing the frame size of the Rectangle including shadow..
ForEach(0 ..< 12) { index in
Rectangle()
.foregroundColor(.white)
.frame(width: 100, height: 100, alignment: .center)
.shadow(radius: 20)
.padding(20) //<< Padding here for the radius
}
I need a MainView that is centered on the screen, and a flexible HeaderView that takes up the remaining space between the MainView and the top of the screen (see below). How do I accomplish this in SwiftUI?
Starter code:
struct TestView: View {
var body: some View {
ZStack {
//Center Line
Divider()
VStack {
Text("HeaderView")
.border(Color.orange, width: 6)
Text("MainView")
.frame(width: 400, height: 200, alignment: .center)
.border(Color.red, width: 6)
}
}
}
}
VStack {
// Let the HeaderView expand to whatever is available, in both directions
Text("HeaderView")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.border(Color.orange, width: 6)
Text("MainView")
.frame(maxWidth: .infinity) // Added to allow this view to expand horizontally
.frame(height: 200) // alignment is not needed here.
.border(Color.red, width: 6)
// And then add a Spacer() at the end that also has flexible height
Spacer()
.frame(maxHeight: .infinity)
}
Since Main is fixed in height, it will get its requested height first. Then since Header and Spacer are equally prioritized and flexible, they will each get half of what remains, causing Main to be centered vertically.