SwiftUI Button Disable styling does not work in some cases - ios

I have the following button in a custom view that I reuse in multiple places
Button(action: { selectedDate = date }) {
VStack {
Text(day.shortName)
.font(.caption2)
.foregroundColor(isSelectedDate ? .white : .primary)
.frame(maxWidth: .infinity)
Spacer().frame(height: 7)
Text("\(date.dayOfMonth)")
.bold()
.foregroundColor(isSelectedDate ? .white : .primary)
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.purple.brightness(isSelectedDate ? 0 : 0.6))
.clipped()
}.disabled(isInPast)
Dates in the past supposed to be disabled, and I confirmed that they are actually disabled as expected; however, the disabled styling looks different in multiple screens although it's the exact same view being used.
What could cause the disable state to not be styled accordingly in some screens?
In both screenshots dates from 25-29 are disabled
In both usages I simply add the view to a VStack
var body: some View {
VStack {
WeekView(selectedDate: $booking.selectedDate).padding()
var body: some View {
VStack(spacing: 0) {
WeekView(selectedDate: $selectedDate)
.padding(.horizontal)
.padding(.bottom)

Don't rely on primary color. Use a custom color and use isInPast to make an opacity.

Figured it out, the parent in one of the screens had .buttonStyle(.plain) which makes the disabled styling work as expected. So I just dded that to the component itself to make sure the disabled styling is always in place

Related

SwiftUI Picker Label with PickerStyle .menu in iOS HowTo

For some reason Apple decided to drop Label support for Pickers
(tested in iOS15 and iOS16)
For example:
Picker(selection: $gender,
label:
"Select your gender"
, content: {
Text(Gender.male.rawValue).tag(Gender.male)
Text(Gender.female.rawValue).tag(Gender.female)
Text(Gender.nobinary.rawValue).tag(Gender.nobinary)
}).pickerStyle(.automatic)
.font(.largeTitle)
.accentColor(.white)
ignores the label view.
So howto solve that?
To add a Label View out of the box without custom View one has to use Picker in Menu View embedded.
Now one can use modifier on the Menu Label Views and even use logic for the selected text.
Menu {
Picker(selection: $gender,
label: EmptyView(),
content: {
Text(Gender.male.rawValue).tag(Gender.male)
Text(Gender.female.rawValue).tag(Gender.female)
Text(Gender.nobinary.rawValue).tag(Gender.nobinary)
}).pickerStyle(.automatic)
.accentColor(.white)
} label: {
Text(gender == .unselected ? "Select your gender" : gender.rawValue)
.font(.title3)
.frame(maxWidth: .infinity)
.frame(height: 55)
.background(.white)
.cornerRadius(10)
.accentColor(.pink)
}

SwiftUI - can I make one element in Form fill the whole screen width (without horizontal margins)?

I would like a single item inside SwiftUI Form to run from side to side, without having Form's default margins.
Unfortunately, whatever I do (like ading a wider .frame, negative horizontal padding, or .offset), the team image view seems to be always cropped by the form to fit the form area (= has horizontal margins).
Is it possible to make the Image below touch the left and right side of the screen?
I am using Form for my app settings, but I would like to add a full-width section there (think eg. a banner to promote a feature).
SwiftUI Playgrounds code:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
Form {
Section(
header: Text("First section")
) {
Text("Hello world")
}
Text("The image below should be stretched to touch the left and right window edge, without being cropped by the Form.")
Image(systemName: "sun.max.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.listRowInsets(EdgeInsets()) // this is supposed to fix the problem, but all it does is to set the default item inner padding to zero, so the image at least touches the edge of teal area.
.listRowBackground(Color.teal)
Section(
header: Text("Last section")
) {
Text("Hello world")
}
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
How it looks:
Unfortunately, SwiftUI Form is very temperamental and forces you to strictly adhere to the standard iOS Settings screen formatting.
Fortunately, you can re-implement similar formatting yourself with SwiftUI!
For the top, something like:
VStack(spacing: 4) {
Text("FIRST SECTION")
.font(.system(size: 12, weight: .regular))
.foregroundColor(.gray)
.padding(.leading)
Text("Hello, world!")
.font(.system(size: 15, weight: .regular))
.foregroundColor(.black)
.padding(.horizontal)
.frame(height: 44, alignment: .center)
.background(Color.white)
.cornerRadius(10)
}

SwiftUI modal sheet dismisses itself after half a second

I have got a modal sheet, here is the code:
SettingsDashboardView:
#State private var notificationsSettingsSheet = false
var body: some View {
Button(action: {
self.notificationsSettingsSheet.toggle()
}) {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 4) {
Label("Set Daily Reminders", systemImage: "alarm").foregroundColor(Color("TextColor"))
.font(.system(.headline, design: .rounded))
Spacer()
}
}
}
.sheet(isPresented: $notificationsSettingsSheet) {
NotificationSettingsModal()
}
}
NotificationSettingsModal:
var body: some View {
ZStack(alignment: .bottom) {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Text("Daily Reminders")
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
.padding(.top, headingTopPadding)
.padding(.horizontal, headingHorizontalPadding).foregroundColor(Color("TextColor"))
Spacer().frame(height: 164)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
Spacer().frame(height: 64)
}
}.background(Color("BackgroundColor").edgesIgnoringSafeArea(.all))
}
When I launch the app and open my sheet, in about 50% of cases sheet dismisses itself after about half a second. If I open sheet after that everything works fine. What can cause this problem?
This will probably not solve the mentioned issue but can be useful for others.
In most cases, this issue happens when the view gets redrawn due to a change in some variables. Be careful that it might be the parent view that have some variables changes.
The best way to debug this kind of behaviour is to use the technique describe here, on Hacking with Swift. The idea is to identify what change caused a view to reload itself by printing print(Self._printChanges()) inside the body property. Note that by doing it, you will temporarily need to add an explicit return.
Then, observer the console and it most cases you will be able to identify the issue and refactor your code.
In my experience (does not seem to be the case here) this often happens when using #Environment(\.editMode) var editMode in both the view and parent view. For some reasons this value changes in both views when presenting a sheet, causing the view to be redrawn and the sheet closed.
I solved this problem by removing the codes below while setting to NavigationView on my homeView this week, which caused my subView's sheet automatically dismissed the first time showing.
NavigationView {...}
// .navigationViewStyle(StackNavigationViewStyle())

How to show a view when tapping on a list item?

I am working with a list in SwiftUI, I am attempting to recreate a system I had with TableView whereby a user can tap a cell and then a new view is presented with data relating to said cell. Now we have lists my code has changed to the following:
List {
ForEach(clients, id: \.id) { client in
VStack(alignment: .center) {
HStack{
Text(client.firstName ?? "Unknown" + " ")
.font(.system(size: 17))
.foregroundColor(Color.init(hex: "47535B"))
.fontWeight(.medium)
.multilineTextAlignment(.leading)
.padding(.leading)
Text(client.lastName ?? "Unknown")
.font(.system(size: 17))
.foregroundColor(Color.init(hex: "47535B"))
.fontWeight(.medium)
.multilineTextAlignment(.leading)
Spacer()
}
}
.frame(height: 50.0)
.background(Color.init(hex: "F6F6F6"))
.cornerRadius(7.0)
}
}
.padding(.horizontal, 3.0)
.padding(.vertical, 115.0)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
I realise I can use a NavigationLink and place the entire thing into a NavigationView but this functionality is not what I want, below is an image of my interface. What I am trying to achieve is when the user taps a cell it presents the data in the space on the right where it says "Select a client to view their profile". With the NavigationView setup I can only use the 2 default styles neither of which are suitable for me since I cannot customise where the navigation view gets placed. Is there a way I can register the same tap but have my own custom system for displaying the resulting data where I want in my interface? Perhaps I am wrong about NavigationView or maybe there is a way to have the NavigationView be positioned entirely outside of the view that contains the item list?
If you don't want to or aren't using use a NavigationView, then you likely have both the client list and the client detail in the same view somewhere. I would try adding #State private var selectedClient: Client? = nil to whatever view has both the list and the detail.
First, pass selectedClient as a binding to the list. Next, whenever one of the list items is tapped (achievable through .onTapGesture() or Button), Update selectedClient.
In your detail view, accept a bindable Client? parameter. If it's nil, then just show your current Text view. If it's not nil, then build the detail UI.
Hope this helps!

SwiftUI text-alignment

Among the many properties of the Text view, I couldn't find any related to text alignment. I've seen in a demo that it automatically handles RTL, and when placing stuff using View's body, it always centers it automatically.
Is there some concept that I'm missing about layout system in SwiftUI and if not, how can I set the text alignment properties to the Text?
You can do this via the modifier .multilineTextAlignment(.center).
Text("CENTER")
.multilineTextAlignment(.center)
Apple Documentation
From SwiftUI beta 3 forward, you can center a text view with the frame modifier:
Text("Centered")
.frame(maxWidth: .infinity, alignment: .center)
Was trying to understand this myself as other answers here mention Text.multilineTextAlignment(_:) / VStack(alignment:) / frame(width:alignment:) but each solution solves a specific problem. Eventually it depends on the UI requirement and a combination of these.
VStack(alignment:)
The alignment here is for the inner views in respective to one another.
So specifying .leading would associate all inner views to have their leading aligned with one another.
VStack(alignment: .leading, spacing: 6) {
Text("Lorem ipsum dolor")
.background(Color.gray.opacity(0.2))
Text("sit amet")
.background(Color.gray.opacity(0.2))
}
.background(Color.gray.opacity(0.1))
.frame
In frame(width:alignment:) or frame(maxWidth:alignment:), the alignment is for the contents within the given width.
VStack(alignment: .leading, spacing: 6) {
Text("Lorem ipsum dolor")
.background(Color.gray.opacity(0.2))
Text("sit amet")
.background(Color.gray.opacity(0.2))
}
.frame(width: 380, alignment: .trailing)
.background(Color.gray.opacity(0.1))
The inners views are leading aligned respective to one another but the views themselves are trailing aligned respective to the VStack.
.multilineTextAlignment
This specifies the alignment of the text inside and can be seen best when there are multiple lines otherwise without a defined frame(width:alignment), the width is automatically adjusted and gets affected by the default alignments.
VStack(alignment: .trailing, spacing: 6) {
Text("0. automatic frame\n+ view at parent's specified alignment\n+ multilineTA not set by default at leading")
.background(Color.gray.opacity(0.2))
Text("1. automatic frame\n+ view at parent's specified alignment\n+ multilineTA set to center")
.multilineTextAlignment(.center)
.background(Color.gray.opacity(0.2))
Text("2. automatic frame\n+ view at parent's specified alignment\n+ multilineTA set to trailing")
.multilineTextAlignment(.trailing)
.background(Color.gray.opacity(0.2))
}
.frame(width: 380, alignment: .trailing)
.background(Color.gray.opacity(0.1))
Tests with combinations:
VStack(alignment: .trailing, spacing: 6) {
Text("1. automatic frame, at parent's alignment")
.background(Color.gray.opacity(0.2))
Text("2. given full width & leading alignment\n+ multilineTA at default leading")
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray.opacity(0.2))
Text("3. given full width & center alignment\n+ multilineTA at default leading")
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.gray.opacity(0.2))
Text("4. given full width & center alignment\n+ multilineTA set to center")
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.gray.opacity(0.2))
Text("5. given full width & center alignment\n+ multilineTA set to trailing")
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.gray.opacity(0.2))
Text("6. given full width but no alignment\n+ multilineTA at default leading\n+ leading is based on content, looks odd sometimes as seen here")
.frame(maxWidth: .infinity)
.background(Color.gray.opacity(0.2))
}
.frame(width: 380)
.background(Color.gray.opacity(0.1))
I've actually run into the problem where I had to align text on a single line. What I've found to work is this:
Text("some text")
.frame(alignment: .leading)
If you combine this with the frame width parameter you can get some nice text block formatting for labels and such.
I guess SwiftUI wants us to use wrappers like stacks for such things.
So instead of writing something like Text("Hello World").aligned(.leading), the following is encouraged:
VStack(alignment: .leading) {
Text("Hello World")
}
We need to align the Text and not the Stack it's in. So calling multilineTextAlignment(.center) and setting the line limits I can be able to see the texts aligned to center. I don't know why I have to set the line limits, I thought it would expand if you have a large text.
Text("blahblah")
.font(.headline)
.multilineTextAlignment(.center)
.lineLimit(50)
If you would like to keep constant width for the Text, the ".multilineTextAlignment(.leading)" won't take any effect until there is only one line of text.
This is the solution that worked for me:
struct LeftAligned: ViewModifier {
func body(content: Content) -> some View {
HStack {
content
Spacer()
}
}
}
extension View {
func leftAligned() -> some View {
return self.modifier(LeftAligned())
}
}
Usage:
Text("Hello").leftAligned().frame(width: 300)
I had the same problem.
i used this for fixing that.
Text("Test")
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
You can set alignment for Vertical stackView as leading. Like below
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)
}
I'd like to use Spacer() view to aligning text block.
This example show text at the trailing side:
HStack{
Spacer()
Text("Wishlist")
}
You can always add a frame to the Text field and can modify it's alignment.
Text("Hello World!")
.frame(alignment : .topLeading)
Since, this is just for a couple of lines - this is better than using alignment on either of the Stacks
Not sure if this is the answer you are looking for but I have experienced that SwiftUI automatically switches to RTL for languages like Arabic, you don't need to explicitly specify that like in UIKit.
You can use this property of SwiftUI
multilineTextAlignment
for TextAlignment.
VStack {
Text("Your Text")
.multilineTextAlignment(.center)
}

Resources