I can't figure out how to change the width of Picker() in SwiftUI 2, iOS 14. Is this a bug or am I missing out something? Seems like the width modifier only gets applied onto the Text but not onto the grey bar.
struct ContentView: View {
#State private var position = 0
var body: some View {
VStack {
Spacer()
Picker(selection: self.$position, label: Text("")){
ForEach(0..<50){ i in
Text("\(i)")
}
}
.frame(width: 10, height: 50, alignment: .center)
Spacer()
}
}
}
The width: 10 is too small... anyway, use .clipped (or .clipShape) to restrict drawing outside bounds
Picker(selection: self.$position, label: Text("")){
ForEach(0..<50){ i in
Text("\(i)")
}
}
.frame(width: 50, height: 150, alignment: .center)
.clipped() // << here !!
Related
I wanted some extra space on the top of the list so I tried using Spacer within the list and added modifiers to it. However I am not seeing the height getting reduced further. Below is the code for my view.
CustomView:
import SwiftUI
struct CustomView: View {
var body: some View {
VStack {
List {
Spacer()
.frame(minHeight: 1, idealHeight: 1, maxHeight: 2)
.fixedSize().listRowBackground(Color.clear)
UserLoginDetailsRowView().padding(.init(top: 0, leading: 5, bottom: 5, trailing: 5))
ForEach(1..<2) { _ in
VStack(alignment: .leading) {
Text("App version").fixedSize(horizontal: false, vertical: true).font(.headline).foregroundColor(.white)
Text("1.1.0").fixedSize(horizontal: false, vertical: true).font(.subheadline).foregroundColor(.white)
Spacer()
}.padding(.bottom, 15)
}.listRowBackground(Color.clear)
}
}.navigationBarTitle("Main Menu")
}
}
UserLoginDetailsRowView code:
import SwiftUI
struct UserLoginDetailsRowView: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .center) {
Spacer()
Spacer()
Text("User's full name").lineLimit(2).font(.headline)
Text("Username").lineLimit(2).font(.subheadline)
Spacer()
}
ZStack {
Image("user-gray")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30 , height: 30)
.offset(x: geometry.size.width / 2.8, y: -geometry.size.height/4)
}
}.frame(minHeight: 60.0)
}
}
This is how it looks with this code:
Regardless of the changes I make to minHeight, idealHeight and maxHeight in Spacer() within CustomView the result remains the same. However I want half of the space of what it's currently showing. I even tried replacing Spacer() with VStack and setting a frame height modifier to it, but at minimum, I do always see this much of space. I want the space reduced to half.
If I remove the Spacer() from CustomView then the image on my custom row gets chopped off and looks something like this. How do I reduce the space to half of what it is now?
Adding playground source code:
import SwiftUI
import PlaygroundSupport
struct CustomView: View {
var body: some View {
VStack {
Spacer().frame(minHeight: 25, idealHeight: 25, maxHeight: 30).fixedSize().listRowBackground(Color.clear)
List {
// Extra space for the top half of user icon within UserLoginDetailsRowView.
// Spacer().frame(minHeight: 25, idealHeight: 25, maxHeight: 30).fixedSize().listRowBackground(Color.clear)
UserLoginDetailsRowView().padding(.init(top: 0, leading: 5, bottom: 5, trailing: 5))
ForEach(1..<2) { _ in
VStack(alignment: .leading) {
Text("App Version").fixedSize(horizontal: false, vertical: true).font(.headline).foregroundColor(.white)
Text("1.1.0").fixedSize(horizontal: false, vertical: true).font(.subheadline).foregroundColor(.white)
Spacer()
}.padding(.bottom, 15)
}.listRowBackground(Color.clear)
}
}.navigationBarTitle("Back")
}
}
struct UserLoginDetailsRowView: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .center) {
Spacer()
Spacer()
Text("User's full name").lineLimit(2).font(.headline)
Text("Username").lineLimit(2).font(.subheadline)
Spacer()
}
ZStack {
Image(systemName: "person.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 22 , height: 22)
.offset(x: geometry.size.width / 2.8, y: -geometry.size.height/4)
}
}.frame(minHeight: 60.0)
}
}
PlaygroundPage.current.setLiveView(CustomView())
Solution
The primary gap comes from the list style itself. if you apply .listStyle(PlainListStyle()) to the List it will reduce it to what you are looking for.
List { ... }.listStyle(PlainListStyle())
If you want to further reduce it and control it to the last pixel apply a .onAppear modifier to the list and set the content inset to your desired value.
List { .... }.onAppear(perform: {
UITableView.appearance().contentInset.top = -60
})
In the above code the value 60 is arbitrary in nature and you need to play around to get a value that fits your UI.
Explanation
The List default style adds a larger header which creates the spacing you were having issues with, this behaviour is similar to GroupedListStyle. From the documentation
On iOS, the grouped list style displays a larger header and footer than the plain style, which visually distances the members of different sections.
You can play around with other List Styles from the documentation to fit your needs better.
Full Playground Code - For .onAppear solution
import SwiftUI
import PlaygroundSupport
struct CustomView: View {
var body: some View {
VStack(alignment:.leading, spacing:0) {
List {
// Extra space for the top half of user icon within UserLoginDetailsRowView.
Spacer().frame(minHeight: 1, idealHeight: 1, maxHeight: 2)
.fixedSize().listRowBackground(Color.clear)
UserLoginDetailsRowView().padding(.init(top: 0, leading: 5, bottom: 5, trailing: 5))
ForEach(1..<2) { _ in
VStack(alignment: .leading) {
Text("App Version").fixedSize(horizontal: false, vertical: true).font(.headline).foregroundColor(.white)
Text("1.1.0").fixedSize(horizontal: false, vertical: true).font(.subheadline).foregroundColor(.white)
Spacer()
}.padding(.bottom, 15)
}.listRowBackground(Color.clear)
}.onAppear(perform: {
UITableView.appearance().contentInset.top = -60
})
}.navigationBarTitle("Back")
}
}
struct UserLoginDetailsRowView: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .center) {
Spacer()
Spacer()
Text("User's full name").lineLimit(2).font(.headline)
Text("Username").lineLimit(2).font(.subheadline)
Spacer()
}
ZStack {
Image(systemName: "person.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 22 , height: 22)
.offset(x: geometry.size.width / 2.8, y: -geometry.size.height/4)
}
}.frame(minHeight: 60.0)
}
}
PlaygroundPage.current.setLiveView(CustomView())
I am trying to make a SwiftUI TextEditor with a Divider that adapts its position to stay under the bottom-most line of text inside of a edit-bio section of the app.
Note: I have a frame on my TextEditor so that it doesn't take up the whole-screen
Right now the Divider is static and stays in one place. Is there a built-in way to make the divider stay under the bottom most line of text?
I would think the Spacer would have given me this behavior?
Thank you!
struct EditBio: View {
#ObservedObject var editProfileVM: EditProfileViewModel
var body: some View {
VStack(spacing: 10) {
TextEditor(text: $editProfileVM.bio)
.foregroundColor(.white)
.padding(.top, 70)
.padding([.leading, .trailing], 50)
.frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
Divider().frame(height: 1).background(.white)
Spacer()
}
}
}
It is doing exactly what you told it to do. But a background color on your TextEditor. You will see that it has a height of 200 + a spacing of 10 from the VStack.
I changed your code to make it obvious:
struct EditBio: View {
#State var editProfileVM = ""
var body: some View {
VStack(spacing: 10) {
TextEditor(text: $editProfileVM)
.foregroundColor(.white)
.padding(.top, 70)
.padding([.leading, .trailing], 50)
.frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
.background(Color.gray)
Divider().frame(height: 1).background(.red)
Spacer()
}
}
}
to produce this:
You can see the TextEditor naturally wants to be taller than 200, but that is limiting it. Therefore, the Spacer() is not going to cause the TextEditor to be any smaller.
The other problem that setting a fixed frame causes will be that your text will end up off screen at some point. I am presuming what you really want is a self sizing TextEditor that is no larger than it's contents.
That can be simply done with the following code:
struct EditBio: View {
#State var editProfileVM = ""
var body: some View {
VStack(spacing: 10) {
SelfSizingTextEditor(text: $editProfileVM)
// Frame removed for the image below.
// .frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
.foregroundColor(.white)
// made the .top padding to be .vertical
.padding(.vertical, 70)
.padding([.leading, .trailing], 50)
.background(Color.gray)
Divider().frame(height: 5).background(.red)
Spacer()
}
}
}
struct SelfSizingTextEditor: View {
#Binding var text: String
#State var textEditorSize = CGSize.zero
var body: some View {
ZStack {
Text(text)
.foregroundColor(.clear)
.copySize(to: $textEditorSize)
TextEditor(text: $text)
.frame(height: textEditorSize.height)
}
}
}
extension View {
func readSize(onChange: #escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
func copySize(to binding: Binding<CGSize>) -> some View {
self.readSize { size in
binding.wrappedValue = size
}
}
}
producing this view:
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.
This question already has answers here:
SwiftUI: How to remove margin between views in VStack?
(3 answers)
Closed 2 years ago.
I'm getting weird padding when trying to put another VStack in ZStack. How to remove it?
struct ContentView: View {
var body: some View {
VStack{
VStack{
Text("1")
}
.frame(width: 200, height: 50)
.background(Color.init(.green))
ZStack{
VStack{
Text("2")
}
.frame(width: 210, height: 50)
.background(Color.init(.blue))
VStack{
Text("3")
}
.frame(width: 200, height: 50)
.background(Color.init(.green))
}
VStack{
Text("4")
}
.frame(width: 200, height: 50)
.background(Color.init(.green))
}
}
}
if I comment VStack with Text("2"), padding will disappear.
Here it is
var body: some View {
VStack(spacing: 0) { // << here !!
You have provide different width in
VStack{
Text("2")
}
.frame(width: 210, height: 50)
.background(Color.init(.blue))
the width is 210 and other element width is 200
provide the same width the padding will remove.
import SwiftUI
struct ContentView: View {
#State private var isActive : Bool = false
var body: some View {
VStack {
Image("home-img")
.resizable()
.aspectRatio(contentMode:.fill)
.edgesIgnoringSafeArea(.top)
.edgesIgnoringSafeArea(.bottom)
.overlay(Image("round")
.resizable()
.frame(width: 350, height: 350 , alignment: .center)
.overlay(Image("girl-img")
.resizable()
.frame(width: 150, height: 150, alignment: .center)
)
.overlay(Image("video")
.resizable()
.frame(width: 100, height: 100)
.offset(y: -200)
.padding(.bottom, -70)
).onTapGesture(count: 1) {
NavigationView {
NavigationLink(destination: SelecteImageView(), isActive: self.$isActive) {
Text("")
}
}
}
}
}
}
What I want to do is when I tap on Image("Video") it should redirect me to a new screen using NavigationView. Normally, with button, it redirects to a new screen, but here the problem is that the image is in overlay. Thank you in advance.
You need to put your Image Views inside the NavigationLink label.
The navigationLink acts like Button and it gets the default button style with blue color. So change the default button style to plain using the bellow modifier:
import SwiftUI
struct ContentView: View {
#State private var isActive : Bool = false
var body: some View {
NavigationView {
NavigationLink(destination: SelecteImageView(), isActive: self.$isActive) {
VStack {
Image("home-img") //.renderingMode(.original)
.resizable()
.aspectRatio(contentMode:.fill)
.edgesIgnoringSafeArea(.top)
.edgesIgnoringSafeArea(.bottom)
.clipped() //Here shows your Image only inside the frame
.overlay(Image("round") //.renderingMode(.original)
.resizable()
.frame(width: 350, height: 350 , alignment: .center))
.overlay(Image("girl-img") //.renderingMode(.original)
.resizable()
.frame(width: 150, height: 150, alignment: .center)
)
.overlay(Image("video") //.renderingMode(.original)
.resizable()
.frame(width: 100, height: 100)
.offset(y: -200)
.padding(.bottom, -70)
)
}
}.buttonStyle(PlainButtonStyle())
}
}
}
Or, you can give your Image views .renderingMode(.original) modifier if you are only using Images, and remove .buttonStyle(PlainButtonStyle()) from above code.
Please add tap gesture over here
struct Contentview: View {
#State var isNavigate :Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: LoginView()) {
Image("Birthday")
.resizable()
.aspectRatio(contentMode:.fill)
.edgesIgnoringSafeArea(.top)
.edgesIgnoringSafeArea(.bottom)
.frame(width: 200, height: 200 , alignment: .center)
.overlay(Image("round")
.resizable()
.frame(width: 350, height: 350 , alignment: .center)
)
.overlay(Image("girl-img")
.resizable()
.frame(width: 150, height: 150, alignment: .center)
)
}
}
}
}
}