Adjusting width / horizontalAlignment with SwiftUI - swift

I have the following layout achieved in SwiftUI:
VStack {
TextField(...)
TextField(...)
Button(...)
}
I tried making my VStack to be 50% of the superview, so I added the following modifier:
VStack { ... }
.relativeWidth(0.5)
Not quite 50% of the superview, but I'm more interested in achieve horizontal centering. Anyone know how to achieve that in SwiftUI?
There's a alignmentGuide(_:computeValue:) modifier that's available, but I'm struggling to give the correct inputs to satisfy the compiler.

I'd use some spacers and HStack ...
struct LoginView: View {
#State var myText: String = ""
var body: some View {
HStack {
Spacer()
VStack {
TextField($myText, placeholder: Text("Email"))
.textFieldStyle(.roundedBorder)
.textContentType(.username)
TextField($myText, placeholder: Text("Password"))
.textFieldStyle(.roundedBorder)
.textContentType(.password)
Button(action: {
// Do your login thing here
}) {
Text("Login")
}
}
Spacer()
}
}
}

It seems to be center aligned already. Letting the HStack know how much width to occupy would finish the job perfectly. Above code by #SMP after modifications and without spacers would look like this:
struct LoginView : View {
#State var myText: String = ""
var body: some View {
HStack {
VStack {
TextField($myText, placeholder: Text("Email"))
.textFieldStyle(.roundedBorder)
.textContentType(.username)
TextField($myText, placeholder: Text("Password"))
.textFieldStyle(.roundedBorder)
.textContentType(.password)
Button(action: {
// Do your login thing here
}) {
Text("Login")
}
}
.relativeWidth(0.7)
}
.relativeWidth(1)
}
}

Related

How can I track scrolling with a ScrollView linked to a Custom PageControl - SwiftUI

I want to create a Carousel with SwiftUI(without using TabView)
with a matching/linked Page Control in SwiftUI
So far I have both views and can update the pageControl view with a
#State var pagecontrolTracker updated with a DragGesture() .onChanged but it doesn't update the PageControl if I scroll fast, or sometimes doesn't update at all 😭.
If I Scroll slow tho, the Page Control does update sometimes as expected.
Is there a better way to update this faster and smoother?
I saw .updating modifier for DragGesture() but this doesn't work either
Full View:
struct ContentView: View {
#State var pagecontrolTracker: Int = 0
var body: some View {
VStack {
ScrollView(.horizontal) {
HStack {
ForEach(0...3, id: \.self) { index in
PagingRow()
.gesture(DragGesture().onChanged({ _ in
pagecontrolTracker = index
}))
}
}
}
PagingControls(pagecontrolTracker: $pagecontrolTracker)
}
.padding()
}
}
Inside Custom SwiftUI Row View
struct PagingRow: View {
var body: some View {
VStack {
HStack {
Image(systemName: "globe")
Text("Test Title")
}
.padding()
Button {
print("Test action")
} label: {
Text("Tap Me")
}
.buttonStyle(.borderedProminent)
.padding()
}
.background(Color.orange)
.frame(width: 200)
.cornerRadius(8)
}
}
Custom PageControl in SwiftUI
struct PagingControls: View {
#Binding var pagecontrolTracker: Int
var body: some View {
HStack {
ForEach(0...3, id: \.self) { pagingIndex in
Circle()
.fill(pagecontrolTracker == pagingIndex ? .orange : .black)
.frame(width: 8, height: 8)
}
}
}
}
Note: I don't want to use TabView since I want to be able to show the next upcoming card in the scrollView
A TabView would only show one card per page

NavigationLink keeps aligning my text elements to center instead of leading SwiftUI

I have a CustomSearchBar view that looks like this
However, when I wrap it with NavigationLink, the placeholder text will be centered. And user inputs will be centered too.
How do I maintain the leading alignment while using NavigationLink?
My code structure looks like this:
enum Tab {
case social
}
struct MainAppView: View {
#State var selection: Tab = .social
var body: some View {
TabView(selection: $selection) {
ZStack{
CustomButton()
NavigationView { SocialView() }
}.tabItem{Image(systemName: "person.2")}.tag(Tab.social)
// other tabs....
}
struct SocialView: View {
// ...
var body: some View {
GeometryReader{ geometry in
VStack{
NavigationLink(destination: Text("test")) {
CustomSearchBar()
//...
}.navigationBarHidden(true)
.navigationBarTitle(Text(""))
}
}
}
}
struct CustomSearchBar: View {
var body: some View {
VStack{
HStack {
SearchBarSymbols(// some binding arguments)
CustomTextField(// some binding arguments)
CancelButton(// some binding arguments)
}
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
}
.padding(.horizontal)
}
}
struct CustomTextField: View {
var body: some View {
TextField("friend name", text: $searchText)
.frame(alignment: .leading)
.onTapGesture {
// some actions
}
.foregroundColor(Color("SearchBarSymbolColor"))
.accentColor(Color("SearchBarSymbolColor"))
.disableAutocorrection(true)
}
}
The issues with your code are:
Your navigation view contains the search field. This means that any new view that gets pushed will cover the search field.
Your search field is inside of the navigation link. There are conflicting interactions here as it effectively turns the field into a button, ie tapping the search field vs tapping the navigation link.
Solution:
Move the navigation view below the text field, so that the new view will appear without covering it. Then change the navigation link so that it is activated via a binding that gets triggered when the search field is editing:
struct SocialView: View {
#State private var text: String = ""
#State private var isActive: Bool = false
var body: some View {
GeometryReader{ geometry in
VStack {
CustomTextField(searchText: $text, isActive: $isActive)
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
NavigationView {
NavigationLink(isActive: $isActive, destination: { Text("test") }, label: { EmptyView() })
}
}
}
}
}
struct CustomTextField: View {
#Binding var searchText: String
#Binding var isActive: Bool
var body: some View {
TextField("friend name", text: $searchText) { editing in
self.isActive = editing
} onCommit: {
}
.frame(alignment: .leading)
.disableAutocorrection(true)
}
}

Choppy Animation SwiftUI Nested Views

I'm working on an animation that brings up a view from the bottom of the screen above part of the view that previously occupied the screen. My code is technically working, though I'm concerned that the animation looks too choppy. Basically, what I think is happening is that the new, rising view is composed of several other views, and when I animate it coming up, it also animates the sub-views coming together--something I don't like the look of.
Sample Code:
struct ButtonView: View {
#State var show: Bool = false
var body: some View {
ZStack{
VStack {
Button(action: { withAnimation(.linear(duration: 0.5)) { show = !show }} ) {
Text("Press Me")
}
Rectangle()
.foregroundColor(.gray)
}
}
if show {
VStack {
CollapsibleView()
}
}
}
}
struct CollapsibleView: View {
var body: some View {
ZStack {
VStack {
Text("Text 1")
Text("Text 2")
Text("Text 3")
}
.background(Color.white)
}
}
}
Note that the duration is set to be quite long for illustration purposes, but even at smaller duration values I can still notice the choppy effect.
How do I avoid this? Is there a way to just animate the motion?
Here a way for what you may looking for:
struct ContentView: View {
#State var show: Bool = Bool()
var body: some View {
VStack {
Button(action: { show.toggle() }, label: { show ? Text("hide") : Text("show") })
.animation(nil)
Color.gray
Group {
if show { CollapsibleView().transition(.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .bottom))) }
}
.opacity(show ? 1.0 : 0.0)
}
.animation(Animation.spring(response: 0.4, dampingFraction: 0.4, blendDuration: 1.0), value: show)
}
}
struct CollapsibleView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Text 1")
Text("Text 2")
Text("Text 3")
}
.background(Color.white)
}
}

iOS 14 SwiftUI Keyboard lifts view automatically

I am using TextField in my view and when it becomes the first responder, it lefts the view as shown in the below GIF.
Is there any way I can get rid of this behavior?
Here is my code
NavigationView(content: {
ZStack{
MyTabView(selectedIndex: self.$index)
.view(item: self.item1) {
NewView(title: "Hello1").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item2) {
NewView(title: "Hello2").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item3) {
NewView(title: "Hello3").navigationBarTitle("")
.navigationBarHidden(true)
}
}.navigationBarHidden(true)
.navigationBarTitle("")
}).ignoresSafeArea(.keyboard, edges: .bottom)
// New View
struct NewView:View {
#State var text:String = ""
var title:String
var body: some View {
VStack {
Spacer()
Text("Hello")
TextField(title, text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
.onAppear {
debugPrint("OnApper \(self.title)")
}
}
}
For .ignoresSafeArea to work you need to fill all the available area (eg. by using a Spacer).
The following will not work (no Spacers, just a TextField):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
However, it will work when you add Spacers (fill all the available space):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
If you don't want to use Spacers you can also use a GeometryReader:
struct ContentView: View {
#State var text: String = ""
var body: some View {
GeometryReader { _ in
...
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
You should apply the modifier on the ZStack, NOT the NavigationView
NavigationView(content: {
ZStack{
,,,
}.navigationBarHidden(true)
.navigationBarTitle("")
.ignoresSafeArea(.keyboard, edges: .bottom) // <- This line moved up
})
Full working example:
struct ContentView: View {
#State var text = ""
var body: some View {
VStack{
Spacer()
Text("Hello, World")
TextField("Tap to test keyboard ignoring", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
What eventually worked for me, combining answers posted here and considering also this question, is the following (Xcode 12.4, iOS 14.4):
GeometryReader { _ in
VStack {
Spacer()
TextField("Type something...", text: $value)
Spacer()
}.ignoresSafeArea(.keyboard, edges: .bottom)
}
Both spacers are there to center vertically the textfield.
Using only the GeometryReader or the ignoresSafeArea modifier didn't do the trick, but after putting them all together as shown above stopped eventually the view from moving up upon keyboard appearance.
That's what I figured out:
GeometryReader { _ in
ZStack {
//PUT CONTENT HERE
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
It seems to work for me. In this case you do not need to check iOS 14 availability.

Views compressed by other views in SwiftUI VStack and List

In my SwiftUI application, I'm trying to implement a UI similar to this:
I've added the two rows for category 1 and category 2. The result looks like this:
NavigationView {
VStack(alignment: .leading) {
CategoryRow(...)
CategoryRow(...)
Spacer()
}
.navigationBarTitle(Text("Featured"))
}
Now, when added the view for the third category – an VStack with images – the following happens:
This happened, after I replaced Spacer(), with said VStack:
VStack(alignment: .leading) {
Text("Rivers")
.font(.headline)
ForEach(self.categories["Rivers"]!.identified(by: \.self)) { landmark in
landmark.image(forSize: 200)
}
}
My CategoryRow is implemented as follows:
VStack(alignment: .leading) {
Text(title)
.font(.headline)
ScrollView {
HStack {
ForEach(landmarks) { landmark in
CategoryItem(landmark: landmark, isRounded: self.isRounded)
}
}
}
}
Question
It seems that the views are compressed. I was not able to find any compression resistance or content hugging priority modifiers to fix this.
I also tried to use .fixedSize() and .frame(width:height:) on CategoryRow.
How can I prevent the compression of these views?
Update
I've tried embedding the whole outer stack view in a scroll view:
NavigationView {
ScrollView { // also tried List
VStack(alignment: .leading) {
CategoryRow(...)
CategoryRow(...)
ForEach(...) { landmark in
landmark.image(forSize: 200)
}
}
.navigationBarTitle(Text("Featured"))
}
}
...and the result is worse:
You might prevent the views in VStack from being compressed by using
.fixedSize(horizontal: false, vertical: true)
For example:
I have the following VStack:
VStack(alignment: .leading){
ForEach(group.items) {
FeedCell(item: $0)
}
}
Which render compressed Text()
When I add .fixedSize(horizontal: false, vertical: true)
it doesn't compress anymore
VStack(alignment: .leading){
ForEach(group.items) {
FeedCell(item: $0)
.fixedSize(horizontal: false, vertical: true)
}
}
You could try to add a layoutPriority()operator to your first VStack. This is what the documentation says about the method:
In a group of sibling views, raising a view’s layout priority encourages that view to shrink later when the group is shrunk and stretch sooner when the group is stretched.
So it's a bit like the content compression resistance priority in Autolayout. But the default value here is 0, so you just have to set it to 1 to get the desired effect, like this:
VStack(alignment: .leading) {
CategoryRow(...)
CategoryRow(...)
Spacer()
}.layoutPriority(1)
VStack(alignment: .leading) {
...
}
Hope it works!
It looks like is not enough space for all your views in VStack, and it compresses some of them. You can embed it into the ScrollView
NavigationView {
ScrollView {
VStack(alignment: .leading) {
CategoryRow(...)
CategoryRow(...)
/// you images and so on
}
}
}
struct ContentView1: View {
var body: some View {
NavigationView {
ScrollView {
VStack {
CategoryListView {
CategoryView()
}
CategoryListView {
SquareCategoryView()
}
CategoryListView {
RectangleCategoryView()
}
}
.padding()
}
.navigationTitle("Featured")
}
}
}
struct CategoryListView<Content>: View where Content: View {
private let viewSize: CGFloat = 150
var content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
var body: some View {
VStack {
HStack {
Text("Category name")
Spacer()
}
ScrollView(.horizontal, showsIndicators: false){
HStack {
ForEach(0..<10) { _ in
content()
}
}
}
}
}
}
struct ContentView1_Previews: PreviewProvider {
static var previews: some View {
ContentView1()
}
}
struct CategoryView: View {
private let viewSize: CGFloat = 150
var body: some View {
Circle()
.fill()
.foregroundColor(.blue)
.frame(width: viewSize, height: viewSize)
}
}
struct RectangleCategoryView: View {
private let viewSize: CGFloat = 350
var body: some View {
Rectangle()
.fill()
.foregroundColor(.blue)
.frame(width: viewSize, height: viewSize * 9 / 16)
}
}
struct SquareCategoryView: View {
private let viewSize: CGFloat = 150
var body: some View {
Rectangle()
.fill()
.foregroundColor(.blue)
.frame(width: viewSize, height: viewSize)
}
}
I think your topmost view (in the NavigationView) needs to be a List, so that it is scrollable:
NavigationView {
List {
...
Or use a ScrollView.
A stack automatically fits within a screen. If you want your content to exceed this, you would have used a ScrollView or a TableView etc i UIKit
EDIT:
Actually, a little Googling brought this result, which seems to be exactly what you are making:
https://developer.apple.com/tutorials/swiftui/composing-complex-interfaces