SwiftUI Picker does not respect frame size on device - ios

My Picker view goes beyond the frame size only on device, while works fine in simulator. This view is not a part of a Form or List, just inside a VStack
var body: some View {
HStack {
Text(title)
.foregroundColor(.gray)
Spacer()
if isEditing {
Picker(selection: $value) {
Section {
Text("None").tag("None")
}
Section {
ForEach(Countries.all, id: \.self) { i in
Text(i).tag(i)
}
}
} label: {}
.frame(height: DocumentPage.rowHeight) // <-- This is ignored
} else {
// View when not editing
}
}
.frame(height: DocumentPage.rowHeight) // <-- And this is ignored
}
On the Simulator vs. on device

Using .layoutPriority(1) on the Picker() and .fixedSize() on the Text() helped.

Related

Extraneous alignment of a SwiftUI.Menu when animated by the keyboard appearing

I'm facing a weird alignment issue with a floating menu when the keyboard appears and I've no idea if there's a workaround. To reproduce the issue, simply copy and paste this code:
struct ContentView: View {
#State var searchQuery = ""
var customToolbarButton: some View {
VStack {
Text("Example").font(.footnote)
Image(systemName: "arrowtriangle.down.fill").imageScale(.small)
}.foregroundColor(.accentColor)
}
var body: some View {
NavigationView {
Rectangle().fill(.red)
.safeAreaInset(edge: .bottom) {
HStack(alignment: .center) {
customToolbarButton
Spacer()
Menu { } label: {
customToolbarButton
}
}
.frame(height: 52)
.padding(.horizontal)
.background(Color.white)
.clipShape(Capsule(style: .continuous))
}
}.searchable(text: $searchQuery)
}
}
When the keyboard appears and moves the HStack up, the views that are a Menu are wrongly placed, all other views seem fine. Any idea if there's a fix?
Video to illustrate the issue: https://twitter.com/xmollv/status/1616851571832229889

How to put tabs inside a scrollview in SwiftUI?

So basically I am very new to SwiftUI(started a few days ago) and am trying to put tabs within a ScrollView. The end result I am trying to achieve is that of Instagram's profile view.
I'd imagine the view's implementation would be something like this:
ScrollView {
VStack {
HStack {
// Profile Pic
// Stats
}
// Bio
// Button(s)
LazyVStack(pinnedViews: .sectionHeaders) {
Section {
TabView {
// Tab 1
// Tab 2
// Tab 3
}
} headers: {
// Tab icons
}
}
}
}
The problem is, the TabView never appears. Also I am unsure whether this is the best setup especially the LazyVStack as I am only using to for the fact that it pins the headers. As I said previously, I'm super new to SwiftUI so there are definetly some views that I have no idea exist some of which might be useful in what I am trying to achieve.
Nonetheless, how can I achieve the layout I am going for?
Thank you!
Side Question: With the way that I have the view setup, the scrollbar for the view is for the entire view however in apps like Instagram, the scrollbars are only within the tabs themselves. How could I also incorporate that aspect into the solution? Thanks again!
You are on the right track. TabView when not used as the root loses the ability to resize it self properly.
So that's why we need to specfiy its minHeight. You can use GeometryReader for that.
Then just give TabView a selection in order for the Tab-Buttons to work and most likely you want to apply .tabViewStyle(.page(indexDisplayMode: .never)) in order to be able to swipe between them.
Working Demo:
GeometryReader { proxy in
ScrollView {
VStack {
HStack {
Image(systemName: "person.circle")
Text("Some Text")
}
}
LazyVStack(spacing: 0, pinnedViews: .sectionHeaders) {
Section {
TabView(selection: $tabIndex) {
VStack {
Spacer()
Text("1")
Spacer()
}
.frame(maxWidth: .infinity)
.background(.red.opacity(0.5))
.tag(0)
VStack {
Spacer()
Text("2")
Spacer()
}
.frame(maxWidth: .infinity)
.background(.green.opacity(0.5))
.tag(1)
VStack {
Spacer()
Text("3")
Spacer()
}
.frame(maxWidth: .infinity)
.background(.blue.opacity(0.5))
.tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(maxWidth: .infinity, minHeight: proxy.size.height)
} header: {
HStack {
Button {
withAnimation {
tabIndex = 0
}
} label: {
Text("Tab 1")
}
.buttonStyle(.bordered)
Button {
withAnimation {
tabIndex = 1
}
} label: {
Text("Tab 2")
}
.buttonStyle(.bordered)
Button {
withAnimation {
tabIndex = 2
}
} label: {
Text("Tab 3")
}
.buttonStyle(.bordered)
}
.padding()
.frame(maxWidth: .infinity)
.background(.regularMaterial)
}
}
}
}
As to your side question: They most likely still use a single scrollview but then move the inset of the scroll indicators. You cannot do that with SwiftUI only but you would need to introspect the underlying UIScrollView for that. SwiftUI-Introspect is good for that.
And then adjust the verticalScrollIndicatorInsets to your needs

ScrollViewReader not scrolling to the correct id with anchor

Problem
First time when the button "Go to 990" is tapped the scroll view doesn't scroll to 990 on top. (see screenshot below)
The anchor is not respected.
Second time it scrolls to 990 top correctly.
Questions
What is wrong and how can it be fixed?
Versions
Xcode 14
Code
struct ContentView: View {
#State private var scrollProxy: ScrollViewProxy?
var body: some View {
NavigationStack {
ScrollViewReader { proxy in
List(0..<1000, id: \.self) { index in
VStack(alignment: .leading) {
Text("cell \(index)")
.font(.title)
Text("text 1")
.font(.subheadline)
Text("text 2")
.font(.subheadline)
}
}
.onAppear {
scrollProxy = proxy
}
}
.toolbar {
ToolbarItem {
Button("Go to 990") {
scrollProxy?.scrollTo(990, anchor: .top)
}
}
}
}
#if os(macOS)
.frame(minWidth: 500, minHeight: 300)
#endif
}
}
Screenshot

SwiftUI change view from first screen to tabview screen

I want to change views once the user taps 'get started' but due to having navigation view in my first view, it is showing back button on my next screen which I don't want. Please see the images attached below.
Code for the first view is below:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
NavigationLink(destination: ViewChanger()) {
Text("Get Started")
.font(.headline)
.navigationBarBackButtonHidden(true)
}
}
.padding()
}
}
}
back button showing on screen 2
First view
Change the location of your navigationBackButtonHidden modifier so that it actually modifies the view that you're going to (and not the NavigationLink label):
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
NavigationLink(destination: ViewChanger()
.navigationBarBackButtonHidden(true) // <-- Here
) {
Text("Get Started")
.font(.headline)
}
}
.padding()
}
}
}
If you want not only the back button to be gone, but the entire header bar, you can use the .navigationBarHidden(true) modifier.
Also, if you run this on iPad at all, you probably want .navigationViewStyle(StackNavigationViewStyle()) added to the outside of your NavigationView
If you use a NavigationLink (in a NavigationView), the view will be pushed. If you want to replace the view, you can do this with an if statement.
For example, this could be implemented like this:
struct ContentView: View {
#State var showSecondView: Bool = false
var body: some View {
if !showSecondView {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
Button(action: { showSecondView = true }) {
Text("Get Started")
.font(.headline)
}
}
.padding()
} else {
TabView {
// ...
}
}
}
}

SwiftUI HStack fill whole width with equal spacing

I have an HStack:
struct BottomList: View {
var body: some View {
HStack() {
ForEach(navData) { item in
NavItem(image: item.icon, title: item.title)
}
}
}
}
How do I perfectly center its content with equal spacing automatically filling the whole width?
FYI just like Bootstraps CSS class .justify-content-around
The frame layout modifier, with .infinity for the maxWidth parameter can be used to achieve this, without the need for an additional Shape View.
struct ContentView: View {
var data = ["View", "V", "View Long"]
var body: some View {
VStack {
// This will be as small as possible to fit the data
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.border(Color.red)
}
}
// The frame modifier allows the view to expand horizontally
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity)
.border(Color.red)
}
}
}
}
}
The various *Stack types will try to shrink to the smallest size possible to contain their child views. If the child view has an ideal size, then the *Stack will not expand to fill the screen. This can be overcome by placing each child on top of a clear Rectangle in a ZStack, because a Shape will expand as much as possible. A convenient way to do this is via an extension on View:
extension View {
func inExpandingRectangle() -> some View {
ZStack {
Rectangle()
.fill(Color.clear)
self
}
}
}
You can then call it like this:
struct ContentView: View {
var data = ["View", "View", "View"]
var body: some View {
VStack {
// This will be as small as possible to fit the items
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.border(Color.red)
}
}
// Each item's invisible Rectangle forces it to expand
// The .fixedSize modifier prevents expansion in the vertical direction
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.inExpandingRectangle()
.fixedSize(horizontal: false, vertical: true)
.border(Color.red)
}
}
}
}
}
You can adjust the spacing on the HStack as desired.
I inserted Spacer() after each item...but for the LAST item, do NOT add a Spacer():
struct BottomList: View {
var body: some View {
HStack() {
ForEach(data) { item in
Item(title: item.title)
if item != data.last { // match everything but the last
Spacer()
}
}
}
}
}
Example list that is evenly spaced out even when item widths are different:
(Note: The accepted answers .frame(maxWidth: .infinity) did not work for all cases: it did not work for me when it came to items that have different widths)
If items are fullwidth compatible, it will be done automatically, you can wrap items between spacers to make it happen:
struct Resizable: View {
let text: String
var body: some View {
HStack {
Spacer()
Text(text)
Spacer()
}
}
}
So you. can use it in a loop like:
HStack {
ForEach(data, id: \.self) { item in
Resizable(text: item)
}
}
You can also use spacing in stacks ... ie
HStack(spacing: 30){
Image("NetflixLogo")
.resizable()
.scaledToFit()
.frame(width: 40)
Text("TV Show")
Text("Movies")
Text("My List")
}
.frame(maxWidth: .infinity)
output result looks like this ...
If your array has repeating values, use array.indices to omit a spacer after the last element.
HStack() {
ForEach(data.indices) { i in
Text("\(data[i])")
if i != data.last {
Spacer()
}
}
}

Resources