SwiftUI ForEach vertical divider layout strange - ios

I'm beginner in SwiftUI and faced strange behaviour of ForEach layout when adding some VStack with divider inside.
Here is example:
struct TestUserView: View {
#State var users: [String] = ["John Doe",
"Jane Doe",
"James Doe",
"Judy Doe"]
var body: some View {
VStack {
ForEach(users, id: \.self) { user in
VStack(spacing: 0) {
// Text("")
// .background(Color.blue)
// .frame(height: 0)
//
Rectangle()
.frame(height: 5)
HStack {
Text(user)
.font(.system(size: 55, weight: .bold))
Spacer()
}
}
.background(Color.green)
}
}
.background(Color.purple)
}
}
It looks like this:
See this purple area. I don't expect it to be there.
Strange enough that it disappears if add Text before separator(no matter if this is Rectangle, Divider or Color.black). Just uncomment commented code and it will become as expected.
Just wondering if this is bug or I don't understand SwiftUI layout.
If this is expected please point me to some useful documentation.
Used XCode 11.5

It is effect of default spacing between different kind of views.
Here is a solution. Tested with Xcode 11.4 / iOS 13.4.
var body: some View {
VStack(spacing: 0) { // << explicit spacing !!
// .. other code

Related

SwiftUI ScrollView extra padding when go to another screen with showed keyboard

The default "Keyboard Avoidance" SwiftUI is used.
First GIF
If you put the code in VStack and add another View to it, then the container rises
Second GIF
I don't want to delete Keyboard Avoidance. I need to remove extra spacing
scrollDismissesKeyboard for ScrollView is not an option
minimal iOS version is iOS 16
struct ContentView: View {
#State var text: String = "Bu bu?"
var body: some View {
NavigationStack {
ScrollViewReader { proxy in
VStack {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
Spacer()
.frame(height: 500)
TextField("", text: $text)
.padding(.bottom, 70)
.frame(height: 40)
.frame(maxWidth: .infinity)
.background(Color.red)
NavigationLink("Screen 2", destination: {
Text("SwiftUI - Nice to meet you, let's never meet again")
})
}
}
Text("I'm in VSTack after scroll view")
}
}
}
}
}
I looked it up with a hierarchy view, and noticed that a UIInputSetHostView is created with a height of 216
View hierarchy 1
View hierarchy 2
disableAutocorrection not working

In SwiftUI how can I have a View exceed its parents frame/boundary in a List?

Consider the following code:
import SwiftUI
struct ContentView: View {
private var listContent: [String] = ["This", "is", "a", "placeholder", "list"]
var body: some View {
List {
ForEach(listContent, id: \.self) { entry in
listElement(text: entry)
}
}
}
}
struct listElement: View {
#State var text: String
var body: some View {
Section() {
HStack {
Rectangle()
.fill(.green)
.frame(width: 30, height: nil)
Text(text)
}
.listRowInsets(EdgeInsets())
}
}
}
which generates this
I would like to add additional markers to the left of each List element and/or section, like this (mockup):
I have the data that describes the width of the green bars leading into the list elements on the child and parent element. Coming from web development I expect to do something like a child element having negative margin and is thus exceeding its parents container. However I could not get it to work with SwiftUI's padding. Is there a way to do this?
For example I have tried the following modification to the previously mentioned listElement:
var body: some View {
Section() {
HStack {
Rectangle()
.fill(.red)
.frame(width: 30, height: 10)
.padding(.leading, -20)
.zIndex(9999)
Rectangle()
.fill(.green)
.frame(width: 30, height: nil)
Text(text)
}
.listRowInsets(EdgeInsets())
}
}
which results in:
The red rectangle overflow is hidden. I've tried pushing it to the top with the dreaded zIndex hack but it doesn't work. Even setting the Lists .scrollContentBackground to .hidden and the .background to .clear doesn't show the red Rectangle. I need the overflow to be visible.

SwiftUI expandable component Animation issue

I created a custom bottom bar with horizontal expandable tabs.
I have two animations: (1) tab expand/collapse animation, (2) tab bar translation animation (when some tab was expanded, it affects move other tabs)
struct AirTabView: View {
#Binding var isActive: Bool
var model: TabModel
var action: (() -> ())
var body: some View {
HStack(spacing: 10) {
Image(model.imageName)
.foregroundColor(.black)
if isActive {
Text(model.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.init(uiColor: .label)
)
.lineLimit(1)
}
}
.padding(10)
.background(isActive ? Color(.secondarySystemBackground) : .clear)
.cornerRadius(11)
.onTapGesture(perform: action)
.animation(.linear(duration: 2), value: isActive)
}
}
struct AirTabBar: View {
var tabs: [TabModel]
var actions: [TabActionModel]
#State private var selectedIndex = 0
var body: some View {
HStack(spacing: 10) {
ForEach(0..<tabs.count, id: \.self) { index in
AirTabView(isActive: .constant(selectedIndex == index), model: tabs[index]) {
selectedIndex = index
}
}
Spacer()
ForEach(0..<actions.count, id: \.self) { index in
AirTabActionView(model: actions[index])
}
}
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background()
.cornerRadius(16)
.shadow(
color: .init(uiColor: .black
.withAlphaComponent(0.07)
),
radius: 15,
x: 2)
.animation(.linear(duration: 2))
}
}
But sometimes, I have a visual bug when text that appears in an expanded cell overlaps the image at the animation start. I want that text always be and appear right side of the image.
Please explain to me what I did wrong. Sometimes RIGHT behavior happens, but I want to understand and fix WRONG
Expected effect is not clear, but observed behavior is due to transition (by default it is opacity), ie. when text is added conditionally it appears with opacity transition.
Here is a demo how it could be managed (so you can tune more if some other effect is needed).
Tested with Xcode 13.4 / iOS 15.5 (some missed things replaced)
Main part:
HStack(spacing: 10) {
if isActive {
HStack {
Image(systemName: model.imageName) // system name for testing !!
.foregroundColor(.black)
Text(model.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.init(uiColor: .label)
)
.lineLimit(1)
}
.transition(.identity) // content is same !!
} else {
Image(systemName: model.imageName)
.foregroundColor(.black)
.transition(.identity) // same is here !!
}
}
.animation(.none, value: isActive) // << don't animate !!
.padding(10)
so content of label is not animated and replaced Image <> Image Title, which gives title always appears right after image, and only highlighting box is animated.
Test module on GitHub

Custom ScrollView Indicator in SwiftUI

Is it possible to create a custom horizontal indicator that has empty and filled circles to show how many images there are and the current position?
The below attempt uses a lazyHStack and OnAppear but, judging from the console output, it doesn't work properly since scrolling back and forth doesn't recall the onAppear consistently.
import SwiftUI
struct ContentView: View {
let horizontalScrollItems = ["wind", "hare.fill", "tortoise.fill", "rosette" ]
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(horizontalScrollItems, id: \.self) { symbol in
Image(systemName: symbol)
.font(.system(size: 200))
.frame(width: geometry.size.width)
.onAppear(){print("\(symbol)")}
}
}
}
}
}
}
This is the desired indicator. I'm just not sure how to properly fill and empty each circle as the user scrolls back and forth. Appreciate the help!
You can get the desired result using TabView() and PageTabViewStyle()
Note : This will work from SwiftUI 2.0
Here is the code :
struct ContentView: View {
let horizontalScrollItems = ["wind", "hare.fill", "tortoise.fill", "rosette" ]
var body: some View {
GeometryReader { geometry in
TabView(){
ForEach(horizontalScrollItems, id: \.self) { symbol in
Image(systemName: symbol)
.font(.system(size: 200))
.frame(width: geometry.size.width)
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
Result :

SwiftUI - How can a TextField increase dynamically its height based on its content?

I am trying to build a "chat" view using SwiftUI and I would like to know how can I do in order to increase the height dynamically of a TextField where the users should write their messages.
I have defined a minHeight expecting that the TextField could increase its height based on its intrinsic content.
My current view code:
struct MessageSenderView: View {
#Binding var userTextInput: String
var body: some View {
VStack {
HStack(alignment: .center, spacing: 17) {
senderPlusImage()
ZStack {
Capsule()
.fill(Color("messagesBankDetailColor"))
.frame(minHeight: 34, alignment: .bottom)
HStack(spacing: 15){
Spacer()
ZStack(alignment: .leading) {
if userTextInput.isEmpty { Text(Constants.Login.Text.userPlaceHolder).foregroundColor(Color.white) }
TextField(" ", text: $userTextInput)
.multilineTextAlignment(.leading)
.frame(minHeight: CGFloat(34))
.foregroundColor(Color.white)
.background(Color("messagesBankDetailColor"))
.onAppear { self.userTextInput = "" }
}
arrowImage()
}
.frame(minHeight: CGFloat(34))
.padding(.trailing, 16)
.layoutPriority(100)
}
}
.padding(16)
}
.background(Color("mainBackgroundColor"))
}
}
And here is how it looks like:
Thank you!!!!
For this purpose, you should use UITextfield with the UIViewRepresentable protocol.
Maybe this tutorial can help you : Dynamic TextField SwiftUI
To support multiline text, you should use TextEditor instead of TextField.
If you want it to grow as you type, embed it with a label like below:
ZStack {
TextEditor(text: $text)
Text(text).opacity(0).padding(.all, 8) // <- This will solve the issue if it is in the same ZStack
}
Demo

Resources