This is what I have in code:
VStack(spacing: 2) {
Text("23:11:45") or "23:11"
.foregroundColor(Color(uiColor: mode.underlayBackgroundColor))
.font(.liberationMonoRegular(withSize: 46))
.multilineTextAlignment(.center)
.scaledToFill()
.minimumScaleFactor(0.5)
ProgressView(value: progress, total: 1)
.tint(Color(uiColor: mode.underlayBackgroundColor))
.foregroundColor(Color(uiColor: mode.underlayBackgroundColor)
.opacity(0.3))
}
.fixedSize()
.background(.blue)
VStack is wrapped into:
ScrollView {
}
.background(.red)
.frame(maxWidth: .infinity)
here is version for "23:11:45"
here is version for "23:11"
As you can see the Text with 23:11:45 String extends VStack although it should change the scale of the font. What to do to make it working? Maximum width is 100% - padding for both sides.
Your use of fixedSize propagates down and tells the Text not to scale down.
Compare:
Here's the code that generated that image:
VStack(spacing: 10) {
Text("with fixedSize:")
VStack {
Text("Hello world")
Text("Hello world")
.font(Font.custom("Palatino", fixedSize: 46))
}
.fixedSize()
.minimumScaleFactor(0.5)
.frame(width: 50)
.background(Color.black.opacity(0.2))
Spacer().frame(height: 40)
Text("without fixedSize:")
VStack {
Text("Hello world")
Text("Hello world")
.font(Font.custom("Palatino", fixedSize: 46))
}
.minimumScaleFactor(0.5)
.frame(width: 50)
.background(Color.black.opacity(0.2))
}
.lineLimit(1)
It doesn't matter if the fixedSize comes before or after the minimumScaleFactor. It does matter if it's before or after the frame.
I have a view where the basic structure is as follows (this is just a bare representation of my actual view obviously):
var body: some View {
NavigationView {
VStack {
// Logo and postcode search
VStack {
Text("Logo")
.background(Color.red)
Text("Title")
.background(Color.blue)
}
.fixedSize(horizontal: false, vertical: true)
.padding()
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading) {
Text("Selector")
.background(Color.yellow)
Text("Browse")
.frame(maxHeight: .infinity)
.background(Color.green)
}
.navigationBarHidden(true)
}
}
.background(Color.red)
}
}
What I am trying to do is have the "Browse" section fill all the available space from its starting position to the bottom of the screen. However, here is how it currently looks:
If I set a concrete height to the 'browse' text (e.g. frame(height: 400)) it increases the height accordingly. However, given that the view is within the scroll view which we can see in turn is the full height of the screen, I thought that setting the maxHeight property to .infinity would have the desired effect, but clearly not. What am I doing wrong here?
We need to calculate that manually, because ScrollView requires finite intrinsic content size.
Here is a possible approach. Tested with Xcode 13.4 / iOS 15.5
GeometryReader { gp in
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading) {
Text("Selector")
.background(Color.yellow)
Text("Browse")
.frame(minHeight: height, alignment: .top)
.background(GeometryReader {
Color.clear.preference(key: ViewOffsetKey.self,
value: $0.frame(in: .named("parent")).origin.y)
})
.background(Color.green)
}
.navigationBarHidden(true)
}
.coordinateSpace(name: "parent")
.onPreferenceChange(ViewOffsetKey.self) {
height = gp.size.height - $0
}
.frame(maxWidth: .infinity)
}
Complete code in project
I am trying to right-align several overlays but am unable to figure out how to do this.
What I have here is a VStack of 2 images on the right side of the screen, and I want to display an overlay text label for each image, to the left of the image, but right-aligned with the other labels, like so:
A LABEL A
ANOTHER LABEL B
The code below displays the labels center aligned, like so:
A LABEL A
ANOTHER LABEL B
struct TestOverlayOffset : View {
var body : some View {
HStack {
Spacer()
VStack(spacing: 32) {
Spacer()
Image(systemName: "a.circle").font(.title)
.overlay(labelOnTheLeft("A LABEL"))
Image(systemName: "b.circle").font(.title)
.overlay(labelOnTheLeft("ANOTHER LABEL"))
Spacer()
}
.background(Color.gray)
}
.background(Color.green)
}
func labelOnTheLeft(_ text: String) -> some View {
GeometryReader { proxy in
Text(text)
.fixedSize()
.foregroundColor(.black)
.background(Color.yellow)
.offset(x: -128 - proxy.size.width/2)
}
}
}
Here is possible solution (with smallest changes and removed hardcoding).
Tested with Xcode 12.1 / iOS 14.1
func labelOnTheLeft(_ text: String) -> some View {
GeometryReader { proxy in
Text(text)
.fixedSize()
.foregroundColor(.black)
.background(Color.yellow)
.padding(.trailing)
.offset(x: -proxy.size.width)
.frame(width: proxy.size.width, alignment: .trailing)
}
}
i have an image that i want to strech only it's height to fit different content, how do i do that in swiftUI? right now it looks like this
struct ContentView: View {
var body: some View {
VStack {
HStack {
VStack(alignment: .leading, spacing: 130) {
Text("Title")
.font(.headline)
.foregroundColor(.primary)
Text("text")
.font(.subheadline)
.foregroundColor(.secondary)
Text("padding")
}
.padding(.vertical)
Spacer()
Image("rightTag")
.resizable(capInsets: .init(top: 0, leading: 0, bottom: 0, trailing: 0), resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 20)
}
.frame(maxWidth: screen.width - 60)
.padding(.leading)
.background(.white)
.cornerRadius(20)
}
}
}
how can i stretch its height to fit this outer frame? ragular resizable and stuff can't get it done.
any helped would be wonderful! Thanks!
sry i didn't make myself clear earllier.
Here is possible approach. However as I see now you'd rather need to stretch not the entire original image, but only middle of it, so in real project it would be needed to make your image tri-part and apply below stretching approach only to middle (square) part.
Approach uses asynchronous state update, so works in Live Preview / Simulator / RealDevice. (Tested with Xcode 11.2 / iOS 13.2)
struct ContentView: View {
#State private var textHeigh: CGFloat = .zero
var body: some View {
VStack {
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 130) {
Text("Title")
.font(.headline)
.foregroundColor(.primary)
Text("text")
.font(.subheadline)
.foregroundColor(.secondary)
Text("padding")
}
.padding(.vertical)
.alignmentGuide(.top, computeValue: { d in
DispatchQueue.main.async {
self.textHeigh = d.height // !! detectable height of left side text
}
return d[.top]
})
Spacer()
Image("rightTag")
.resizable()
.frame(maxWidth: 20, maxHeight: max(60, textHeigh)) // 60 just for default
}
.frame(maxWidth: screen.width - 60)
.padding(.leading)
.background(Color.white)
}
}
}
The background area of my button is not detecting user interaction. Only way to interact with said button is to tap on the Text/ Label area of the button. How to make entire Button tappable?
struct ScheduleEditorButtonSwiftUIView: View {
#Binding var buttonTagForAction : ScheduleButtonType
#Binding var buttonTitle : String
#Binding var buttonBackgroundColor : Color
let buttonCornerRadius = CGFloat(12)
var body: some View {
Button(buttonTitle) {
buttonActionForTag(self.buttonTagForAction)
}.frame(minWidth: (UIScreen.main.bounds.size.width / 2) - 25, maxWidth: .infinity, minHeight: 44)
.buttonStyle(DefaultButtonStyle())
.lineLimit(2)
.multilineTextAlignment(.center)
.font(Font.subheadline.weight(.bold))
.foregroundColor(Color.white)
.border(Color("AppHighlightedColour"), width: 2)
.background(buttonBackgroundColor).opacity(0.8)
.tag(self.buttonTagForAction)
.padding([.leading,.trailing], 5)
.cornerRadius(buttonCornerRadius)
}
}
The proper solution is to use the .contentShape() API.
Button(action: action) {
HStack {
Spacer()
Text("My button")
Spacer()
}
}
.contentShape(Rectangle())
You can change the provided shape to match the shape of your button; if your button is a RoundedRectangle, you can provide that instead.
I think this is a better solution, add the .frame values to the Text() and the button will cover the whole area 😉
Button(action: {
//code
}) {
Text("Click Me")
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .center)
.foregroundColor(Color.white)
.background(Color.accentColor)
.cornerRadius(7)
}
You can define content Shape for hit testing by adding modifier: contentShape(_:eoFill:)
And important thing is you have to apply inside the content of Button.
Button(action: {}) {
Text("Select file")
.frame(width: 300)
.padding(100.0)
.foregroundColor(Color.black)
.contentShape(Rectangle()) // Add this line
}
.background(Color.green)
.cornerRadius(4)
.buttonStyle(PlainButtonStyle())
Another
Button(action: {}) {
VStack {
Text("Select file")
.frame(width: 100)
Text("Select file")
.frame(width: 200)
}
.contentShape(Rectangle()) // Add this inside Button.
}
.background(Color.green)
.cornerRadius(4)
.buttonStyle(PlainButtonStyle())
This fixes the issue on my end:
var body: some View {
GeometryReader { geometry in
Button(action: {
// Action
}) {
Text("Button Title")
.frame(
minWidth: (geometry.size.width / 2) - 25,
maxWidth: .infinity, minHeight: 44
)
.font(Font.subheadline.weight(.bold))
.background(Color.yellow).opacity(0.8)
.foregroundColor(Color.white)
.cornerRadius(12)
}
.lineLimit(2)
.multilineTextAlignment(.center)
.padding([.leading,.trailing], 5)
}
}
Is there a reason why you are using UIScreen instead of GeometryReader?
Short Answer
Make sure the Text (or button content) spans the length of the touch area, AND use .contentShape(Rectangle()).
Button(action:{}) {
HStack {
Text("Hello")
Spacer()
}
.contentShape(Rectangle())
}
Long Answer
There are two parts:
The content (ex. Text) of the Button needs to be stretched
The content needs to be considered for hit testing
To stretch the content (ex. Text):
// Solution 1 for stretching content
HStack {
Text("Hello")
Spacer()
}
// Solution 2 for stretching content
Text("Hello")
.frame(maxWidth: .infinity, alignment: .leading)
// Alternatively, you could specify a specific frame for the button.
To consider content for hit testing use .contentShape(Rectangle()):
// Solution 1
Button(action:{}) {
HStack {
Text("Hello")
Spacer()
}
.contentShape(Rectangle())
}
// Solution 2
Button(action:{}) {
Text("Hello")
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
You might be doing this:
Button { /*to do something on button click*/}
label: { Text("button text").foregroundColor(Color.white)}
.frame(width: 45, height: 45, alignment: .center)
.background(Color.black)
Solution:
Button(action: {/*to do something on button click*/ })
{
HStack {
Spacer()
Text("Buttton Text")
Spacer() } }
.frame(width: 45, height: 45, alignment: .center)
.foregroundColor(Color.white)
.background(Color.black).contentShape(Rectangle())
A bit late to the answer, but I found two ways to do this —
Option 1: Using Geometry Reader
Button(action: {
}) {
GeometryReader { geometryProxy in
Text("Button Title")
.font(Font.custom("SFProDisplay-Semibold", size: 19))
.foregroundColor(Color.white)
.frame(width: geometryProxy.size.width - 20 * 2) // horizontal margin
.padding([.top, .bottom], 10) // vertical padding
.background(Color.yellow)
.cornerRadius(6)
}
}
Option 2: Using HStack with Spacers
HStack {
Spacer(minLength: 20) // horizontal margin
Button(action: {
}) {
Text("Hello World")
.font(Font.custom("SFProDisplay-Semibold", size: 19))
.frame(maxWidth:.infinity)
.padding([.top, .bottom], 10) // vertical padding
.background(Color.yellow)
.foregroundColor(Color.white)
.cornerRadius(6)
}
Spacer(minLength: 20)
}.frame(maxWidth:.infinity)
My thought process here is that although option 1 is more succinct, I would choose option 2 since it's less coupled to its parent's size (through GeometryReader) and more in line of how I think SwiftUI is meant to use HStack, VStack, etc.
I was working with buttons and texts that need user interaction when I faced this same issue. After looking and testing many answers (including some from this post) I ended up making it works in the following way:
For buttons:
/* WITH IMAGE */
Button {
print("TAppeD")
} label: {
Image(systemName: "plus")
.frame(width: 40, height: 40)
}
/* WITH TEXT */
Button {
print("TAppeD")
} label: {
Text("My button")
.frame(height: 80)
}
For Texts:
Text("PP")
.frame(width: 40, height: 40)
.contentShape(Rectangle())
.onTapGesture {
print("TAppeD")
}
In the case of the texts, I only need the .contentShape(Rectangle()) modifier when the Text doesn't have a .background in order to make the entire Text frame responsive to tap gesture, while with buttons I use my Text or Image view with a frame and neither a .background nor a .contentShape is needed.
Image of the following code in preview (I'm not allowed to include pictures yet )
import SwiftUI
struct ContentView: View {
#State var tapped: Bool = true
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 19)
.frame(width: 40, height: 40)
.foregroundColor(tapped ? .red : .green)
Spacer()
HStack (spacing: 0) {
Text("PP")
.frame(width: 40, height: 40)
.contentShape(Rectangle())
.onTapGesture {
tapped.toggle()
}
Button {
print("TAppeD")
tapped.toggle()
} label: {
Image(systemName: "plus")
.frame(width: 40, height: 40)
}
.background(Color.red)
Button {
print("TAppeD")
tapped.toggle()
} label: {
Text("My button")
.frame(height: 80)
}
.background(Color.yellow)
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
this way makes the button area expand properly
but if the color is .clear, it dosen't work🤷♂️
Button(action: {
doSomething()
}, label: {
ZStack {
Color(.white)
Text("some texts")
}
})
When I used HStack then it worked for button whole width that's fine, But I was facing issue with whole button height tap not working at corners and I fixed it in below code:
Button(action:{
print("Tapped Button")
}) {
VStack {
//Vertical whole area covered
Text("")
Spacer()
HStack {
//Horizontal whole area covered
Text("")
Spacer()
}
}
}
If your app needs to support both iOS/iPadOS and macOS, you may want to reference my code!
Xcode 14.1 / iOS 14.1 / macOS 13.0 / 12-09-2022
Button(action: {
print("Saved to CoreData")
}) {
Text("Submit")
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 60, alignment: .center)
.foregroundColor(Color.white)
#if !os(macOS)
.background(Color.accentColor)
#endif
}
#if os(macOS)
.background(Color.accentColor)
#endif
.cornerRadius(7)
Easier work around is to add .frame(maxWidth: .infinity) modifier.
and wrap your button inside a ContainerView. you can always change the size of the button where it's being used.
Button(action: tapped) {
HStack {
if let icon = icon {
icon
}
Text(title)
}
.frame(maxWidth: .infinity) // This one
}