SegmentedControl breaks when using ForEach - ios

So I am trying out SwiftUI and unfortunately it still has some issues but I dont know if its on my side or its just buggy. I am trying to use a SegmentedControl alongside a ForEach which displays images but labels of SegmentedControl is gone.
This is my ContentView:
struct ContentView : View {
#State var filter = 0
var body: some View {
NavigationView {
SegmentedControl(selection: $filter) {
Text("Tag1").tag(0)
Text("Tag2").tag(1)
}.padding(.bottom)
ForEach(1...3) { spot in
ZStack {
Image("placeholder")
.frame(height: 200)
.clipped()
Text("Swift")
.font(.largeTitle)
.foregroundColor(.white)
}.padding(.bottom, -8)
}.navigationBarTitle(Text("Explore"))
}
}
}
I dont see why it should break but it does. Am I using it wrongly or is this just a bug?

Your SegmentedControl is stretched and the reason is your image. The SegmentedControl's width is stretched to the size of the image and you're setting a custom frame to the image without making it resizable. That's why your labels are out of bounds. Just add .resizable() to your image, this will fix your image's size and your labels will also appear:
Image("placeholder")
.resizable()
.frame(height: 200)
.clipped()

Related

SwiftUI ScrollView image as background error

Thanks for taking your time to help others :)
Bug description:
I can apply a color to the ScrollView background, and adjusts perfectly.
But if I try to set an image as background, the result is not correct, it expands over to the TextField and even safeArea.
I need this because I'm building a chat. And I can't use a ZStack to put the image under the ScrollView, it's complex to explain why.
Simple piece of code to test it.
import SwiftUI
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
ScrollView() {
LazyVStack {
HStack {
Spacer()
}
ForEach(1..<201, id: \.self) { num in
Text("Message \(num)")
}
}
}
// .background(Color.red) // Good result, adjusted where I want to
.background(Image("chatBackground")) // Bad result, expands to TextField and safe area
TextField("Your text here", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Example app")
}
}
Results:
Good result (using a color):
Bad result (using the image):
Questions
How can I apply an image as background?
Why does it extends down to the very bottom?
EDIT
This is the result of Timmy's answer. Almost the thing but it moves when keyboard appears.
You need to set the background of the TextField to white (or whatever color you need):
TextField("Your text here", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
.background(Color.white)
Update:
In order for the image to bound itself correctly, you need to use the .clipped() modifier after the background one:
.background {
Image("chatBackground")
.resizable() //recommended
.aspectRatio(contentMode: .fill) //recommended
}.clipped()
To make the image ignore the keyboard, you need to wrap the image inside a GeometryReader & add .ignoresSafeArea(.keyboard, edges: .bottom) (Credit to #pawello2222):
.background {
GeometryReader { _ in
Image("chatBackground")
.resizable()
.aspectRatio(contentMode: .fill)
}.ignoresSafeArea(.keyboard, edges: .bottom)
}.clipped()

How come ZStack is not working with device height in SwiftUI?

So I was working on a project and whenever I make a ZStack to get a background color, the subsequent VStack is basically somehow attached to the ZStack. I thought ZStack were similar to objects being stacked on top of each other.
However, the closer I make the bottomHeightMultiplier var closer to 100% the more the Text in the Vstack gets pushed off the screen. I was just trying to create a background view with the top 20% of any device screen being white and the bottom 80% of the screen being green.
Unfortunately the Text("Enter bill total") just ends up getting pushed off the screen. If I put the Spacer() below the Text, it gets pushed to the top and beyond the safe area. Putting it above the Text pushes it to bottom and beyond the safe area.
import SwiftUI
struct CalculatorScreen: View {
var screenWidth = UIScreen.main.bounds.width
var screenHeight = UIScreen.main.bounds.height
var topHeightMultiplier: CGFloat = 0.20
var bottomHeightMultiplier: CGFloat = 0.80
var body: some View {
ZStack {
VStack {
Color.white
.frame(minHeight: screenHeight*topHeightMultiplier)
Color.green
.frame(minHeight: screenHeight*bottomHeightMultiplier)
}
VStack {
Spacer()
Text("Enter bill total")
.foregroundColor(.black)
}
}
}
}
This will do the trick. What really is happening is that when using UIScreen.bounds, the safe areas create a problem, making the view longer than the screen long is. That means that the inner VStack pushes on the outer ZStack and thus makes the ZStack also longer than the device is. Thus, no the objects within the ZStack are completely independent, however the size of the parent changes when that of a child changes, implicitly affecting other child objects in the ZStack. The second VStack will be as long as the ZStack due to the Spacer(), making it look like it has been pushed off the screen.
Additionally, the VStack for the colors had a standard padding (now added spacing: 0), creating a white line between the colors, eating up space as well which is not taken into account when calculating the height of the colors. My tip is to always use colors you can see when building blocks, that way you immediately spot safe area issues and unwanted paddings.
struct CalculatorScreen: View {
var topHeightMultiplier: CGFloat = 0.20
var bottomHeightMultiplier: CGFloat = 0.80
var body: some View {
GeometryReader { geometry in
ZStack {
VStack(spacing: 0) {
Color.purple
.frame(minHeight: geometry.size.height*topHeightMultiplier)
Color.green
.frame(minHeight: geometry.size.height*bottomHeightMultiplier)
}
.ignoresSafeArea()
VStack {
Spacer()
Text("Enter bill total")
.foregroundColor(.black)
}
}
}
}
}

Issues with SwiftUI List NavigationLink frame(height: CGFloat)

I am trying to display a custom view for my List Row, it is an HStack with an image, a title Text and another Text containing a short paragraph. I am having issues with the height of the NavigationLink item. I have tried setting the frame to a specific height but it does not display optimally on different devices (the small resolution difference between the iPhone XR and iPhone XS is enough to make me have to adjust the height manually).
Is there a way to have the frame height be set automatically? I have tried many things such as setting the frame height on my PlanetRow View, adding padding, setting the lineLimit to: 0, 3, 5, ... Whatever I do, if I don't set the height for the NavigationLink View, the PlanetRow extends past the NavigationLink View (visible beyond the List separators).
Here's the code for my views:
struct PlanetRow : View {
var planet: Planet
var body: some View {
HStack {
Image("Mars")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(planet.name)
.font(.largeTitle)
Text(planet.overview)
.font(.caption)
}
.lineLimit(nil)
}
}
}
struct PlanetList : View {
var body: some View {
NavigationView {
List(planetData.identified(by: \.id)) { planet in
NavigationLink(destination: PlanetDetail(planet: planet)) {
PlanetRow(planet: planet)
}
.frame(height: 150)
}
.navigationBarTitle(Text("Planets"))
}
}
}

Prevent SwiftUI System Image from dynamically changing size for navigation bar item

I would like to add a plus button to the navigation bar items using the system plus image in SwiftUI. However, I am unable to prevent the system image from scaling dynamically when the accessibility font changes.
How can I stop it resizing so it acts like a standard UINavigationBarButtonItem system plus button?
The accessibility feature of holding on to a navigation bar button for large font types also doesn't work like it does with UIKit.
Really frustrating that a potentially simple thing can't be done with SwiftUI and that accessibility hasn't been thought about. The SwiftUI tutorials profile bar button also doesn't work for large font sizes. (PS SwiftUI is the future)
Here was my attempt:
struct ContentView : View {
var body: some View {
NavigationView {
List {
Text("Stop + Bar Button resizing")
.lineLimit(nil)
}
.navigationBarTitle(Text("Plus"))
.navigationBarItems(trailing:
PlusNavigationButton()
)
}
}
}
struct PlusNavigationButton: View {
var body: some View {
PresentationButton(
Image(systemName: "plus")
.resizable()
.frame(width: 44, height: 44),
destination: NewView())
}
}
You should build what you exactly want. So if you want to use 16x16 image with some extra hitTest area, you can build like this:
var body: some View {
PresentationButton(
HStack() {
Spacer()
Image(systemName: "plus")
.resizable()
.frame(width: 16, height: 16)
Spacer()
}.frame(width: 44, height: 44),
destination: NewView()
)
}
or if you like some space around your image and let it fill the rest, you can:
var body: some View {
PresentationButton(
Image(systemName: "plus")
.resizable()
.padding(14)
.frame(width: 44, height: 44),
destination: NewView()
)
}

ForEach inside ScrollView doesn't take whole width

I'm trying to re-create UI of my current app using SwiftUI. And it is way more difficult than I initially though.
I wanted to achieve card-like cells with some background behind them. I found that List doesn't support that, at least yet. List is so limited - it doesn't allow you to remove cell separator.
So I moved to ForEach inside ScrollView. I guess that isn't something which should be used in production for long tables but that should work for now. The problem I have is that ForeEach view doesn't take all the width ScrollView provides. I can set .frame(...) modifier but that will require hardcoding width which I definitely don't want to do.
Any ideas how to force VStack take full width of the ScrollView? I tried to use ForeEach without VStack and it has the same issue. It seems like ScrollView (parent view) "tells" its child view (VStack) that its frame is less that actual ScrollView's frame. And based on that information child views build their layout and sizes.
Here is my current result:
And here is the code:
struct LandmarkList : View {
var body: some View {
NavigationView {
ScrollView() {
VStack {
Spacer().frame(height: 160)
ForEach(landmarkData) { landmark in
LandmarkRow(landmark: landmark).padding([.leading, .trailing], 16)
}
}.scaledToFill()
.background(Color.pink)
}
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle(Text("Landmarks"))
}
}
}
struct LandmarkRow : View {
var landmark: Landmark
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(landmark.name).font(.title)
Text("Subtitle")
.font(.callout)
.color(.gray)
}
Spacer()
Text("5 mi")
.font(.largeTitle)
}.frame(height: 80)
.padding()
.background(Color.white)
.cornerRadius(16)
.clipped()
.shadow(radius: 2)
}
}
I've got the same issue, the only way I have found so far is to fix the ScrollView and the content view width, so that every subview you add inside the content view will be centered.
I created a simple wrapper that take the width as init parameter
struct CenteredList<Data: RandomAccessCollection, Content: View>: View where Data.Element: Identifiable {
public private(set) var width: Length
private var data: Data
private var contentBuilder: (Data.Element.IdentifiedValue) -> Content
init(
width: Length = UIScreen.main.bounds.width,
data: Data,
#ViewBuilder content: #escaping (Data.Element.IdentifiedValue) -> Content)
{
self.width = width
self.data = data
self.contentBuilder = content
}
var body: some View {
ScrollView {
VStack {
ForEach(data) { item in
return self.contentBuilder(item)
}.frame(width: width)
}
.frame(width: width)
}
.frame(width: width)
}
}
By default it takes the screen width (UIScreen.main.bounds.width).
It works just like a List view:
var body: some View {
TileList(data: 0...3) { index in
HStack {
Text("Hello world")
Text("#\(index)")
}
}
}
Its possible that the answer to this might just be wrapping your scrollView inside of a GeometryReader
Like done in the answer here -> How do I stretch a View to its parent frame with SwiftUI?

Resources