SwiftUI Section header - use non uppercase? - swiftui

Creating a List as follows:
struct ContentView: View {
var body: some View {
List {
Section(header: Text("Header")) {
Text("Row 1")
Text("Row 2")
}
}
.listStyle(PlainListStyle())
}
}
uses uppercase text in the section header.
Is there a way to force the header text to be retain its original case?

There's a textCase(nil) modifier on Section that honours the original text case, which works on iOS 14
From Apple's developer forums: https://developer.apple.com/forums/thread/655524
Section(header: Text("Section Title")) {
[...]
}.textCase(nil)

For a solution that works with both iOS 13 and 14, you can make a custom modifier that only sets the textCase for iOS 14:
struct SectionHeaderStyle: ViewModifier {
func body(content: Content) -> some View {
Group {
if #available(iOS 14, *) {
AnyView(content.textCase(.none))
} else {
content
}
}
}
}
And then you can apply it to your section like this:
Section(header: Text("Section Name")) {
...
}.modifier(SectionHeaderStyle())
This is an adapted version of a suggestion from apple forums: https://developer.apple.com/forums/thread/650243

I would like to add my solution which I find very convenient dealing with this issue. I simply made a custom text component that I use for section headers.
import SwiftUI
struct SectionHeaderText: View {
var text: String
var body: some View {
if #available(iOS 14.0, *) {
Text(text).textCase(nil)
} else {
Text(text)
}
}
}
I then use it like this:
Section(header: SectionHeaderText(text: "Info"), ...
Mitigates the whole situation with #available(iOS 14.0, *) and gets me the result I want :) Maybe this helps someone out there!

Related

Remove space created on List items in view - SwiftUI [duplicate]

I'm having an issues with a List inside a NavigationView since iOS 14 update.
Here is a simple breakdown of the code - I've striped everything that doesn't show the issue
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
}
}
}
}
This produces the following result:
I cant work out why the list is hovering in the center of the navigation view like that. As far as I can tell this should produce a listview that takes up all avaliable space (with the exception of the top where navigationbar would be).
Indeed when run on iOS 13.5 that is the result I get as pictured below:
I've had a read through the documentation but cant work out why this behaviour is suddenly happening.
Any help would be greatly appreciated.
Thanks
Problem
It looks like the default styles of a List or NavigationView in iOS 14 may in some cases be different than in iOS 13.
Solution #1 - explicit listStyle
It's no longer always the PlainListStyle (as in iOS 13) but sometimes the InsetGroupedListStyle as well.
You need to explicitly specify the listStyle to PlainListStyle:
.listStyle(PlainListStyle())
Example:
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
.listStyle(PlainListStyle()) // <- add here
}
}
}
}
Solution #2 - explicit navigationViewStyle
It looks like the NavigationView's default style can sometimes be the DoubleColumnNavigationViewStyle (even on iPhones).
You can try setting the navigationViewStyle to the StackNavigationViewStyle (as in iOS 13):
.navigationViewStyle(StackNavigationViewStyle())
Example:
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle()) // <- add here
}
}

SwiftUI iOS14 - Disable keyboard avoidance

Is there a way to disable the native keyboard avoidance on iOS14?
There is no keyboard avoidance in iOS13, so I want to implement my own, but when I do the native one on iOS14 is still active, so both my implementation and the native one run at the same time, which I don't want.
My deployment target is iOS13, so I need a solution for both iOS13 and iOS14.
You can use if #available(iOS 14.0, *) if you want to adapt the iOS 14 code so it compiles on iOS 13.
Here is an adapted version of this answer to work on both iOS 13 and iOS 14:
struct ContentView: View {
#State var text: String = ""
var body: some View {
if #available(iOS 14.0, *) {
VStack {
content
}
.ignoresSafeArea(.keyboard, edges: .bottom)
} else {
VStack {
content
}
}
}
#ViewBuilder
var content: some View {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
}

navigationBarItems moves list to the right [duplicate]

I'm having an issues with a List inside a NavigationView since iOS 14 update.
Here is a simple breakdown of the code - I've striped everything that doesn't show the issue
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
}
}
}
}
This produces the following result:
I cant work out why the list is hovering in the center of the navigation view like that. As far as I can tell this should produce a listview that takes up all avaliable space (with the exception of the top where navigationbar would be).
Indeed when run on iOS 13.5 that is the result I get as pictured below:
I've had a read through the documentation but cant work out why this behaviour is suddenly happening.
Any help would be greatly appreciated.
Thanks
Problem
It looks like the default styles of a List or NavigationView in iOS 14 may in some cases be different than in iOS 13.
Solution #1 - explicit listStyle
It's no longer always the PlainListStyle (as in iOS 13) but sometimes the InsetGroupedListStyle as well.
You need to explicitly specify the listStyle to PlainListStyle:
.listStyle(PlainListStyle())
Example:
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
.listStyle(PlainListStyle()) // <- add here
}
}
}
}
Solution #2 - explicit navigationViewStyle
It looks like the NavigationView's default style can sometimes be the DoubleColumnNavigationViewStyle (even on iPhones).
You can try setting the navigationViewStyle to the StackNavigationViewStyle (as in iOS 13):
.navigationViewStyle(StackNavigationViewStyle())
Example:
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle()) // <- add here
}
}

Highlight SwiftUI Button programmatically

I've been trying to highlight programmatically a SwiftUI Button, without success so far…
Of course I could implement the whole thing again, but I'd really like to take advantage of what SwiftUI already offers. I would imagine that there is an environment or state variable that we can mutate, but I couldn't find any of those.
Here's a small example of what I would like to achieve:
struct ContentView: View {
#State private var highlighted = false
var body: some View {
Button("My Button", action: doSomething())
.highlighted($highlighted) // <-- ??
Button("Toggle highlight") {
highlighted.toggle()
}
}
func doSomething() { ... }
}
It seems very odd that something so simple if not within easy reach in SwiftUI. Has anyone found a solution?
Here is a demo of possible approach, based on custom ButtonStyle. Tested with Xcode 11.4 / iOS 13.4
struct HighlightButtonStyle: ButtonStyle {
let highlighted: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.background(highlighted || configuration.isPressed ? Color.yellow : Color.clear)
}
}
extension Button {
func highlighted(_ flag: Bool) -> some View {
self.buttonStyle(HighlightButtonStyle(highlighted: flag))
}
}
struct ContentView: View {
#State private var highlighted = false
var body: some View {
VStack {
Button("My Button", action: doSomething)
.highlighted(highlighted)
Divider()
Button("Toggle highlight") {
self.highlighted.toggle()
}
}
}
func doSomething() { }
}

Why does binding to the Picker not work anymore in swiftui?

When I run a Picker Code in the Simulator or the Canvas, the Picker goes always back to the first option with an animation or just freezes. This happens since last Thursday/Friday. So I checked some old simple code, where it worked before that and it doesn't work for me there, too.
This is the simple old Code. It doesn't work anymore in beta 3, 4 and 5.
struct PickerView : View {
#State var selectedOptionIndex = 0
var body: some View {
VStack {
Text("Option: \(selectedOptionIndex)")
Picker(selection: $selectedOptionIndex, label: Text("")) {
Text("Option 1")
Text("Option 2")
Text("Option 3")
}
}
}
}
In my newer code, I used #ObservedObject, but also here it doesn't work.
Also I don't get any errors and it builds and runs.
Thank you for any pointers.
----EDIT----- Please look at the answer first
After the help, that I could use the .tag() behind all Text()like Text("Option 1").tag(), it now takes the initial value and updates it inside the view. If I use #ObservedObject like here:
struct PickerView: View {
#ObservedObject var data: Model
let width: CGFloat
let height: CGFloat
var body: some View {
VStack(alignment: .leading) {
Picker(selection: $data.exercise, label: Text("select exercise")) {
ForEach(data.exercises, id: \.self) { exercise in
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
}
}
.frame(width: width, height: (height/2), alignment: .center)
}
}
}
}
Unfortunately it doesn't reflect changes on the value, if I make these changes in another view, one navigationlink further. And also it doesn't seem to work with the my code above, where I use firstIndex(of: exercise)
---EDIT---
Now the code above works if I change
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
into
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise)!)
because it couldn't work with an optional.
The answer summarized:
With the .tag() behind the Options it works. It would look like following:
Picker(selection: $selectedOptionIndex, label: Text("")) {
ForEach(1...3) { index in
Text("Option \(index)").tag(index)
}
}
If you use a range of Objects it could look like this:
Picker(selection: $data.exercises, label: Text("")) {
ForEach(0..<data.exercises.count) { index in
Text("\(data.exercises[index])").tag(index)
}
}
I am not sure if it is intended, that .tag() is needed to be used here, but it's at least a workaround.
I found a way to simplify the code a bit without the need of operating on indicies and tags.
At first, make sure to conform your model to Identifiable protocol like this (this is actually a key part, as it enables SwiftUI to differentiate elements):
public enum EditScheduleMode: String, CaseIterable, Identifiable {
case closeSchedule
case openSchedule
public var id: EditScheduleMode { self }
var localizedTitle: String { ... }
}
Then you can declare viewModel like this:
public class EditScheduleViewModel: ObservableObject {
#Published public var editScheduleMode = EditScheduleMode.closeSchedule
public let modes = EditScheduleMode.allCases
}
and UI:
struct ModeSelectionView: View {
private let elements: [EditScheduleMode]
#Binding private var selectedElement: EditScheduleMode
internal init?(elements: [EditScheduleMode],
selectedElement: Binding<EditScheduleMode>) {
self.elements = elements
_selectedElement = selectedElement
}
internal var body: some View {
VStack {
Picker("", selection: $selectedElement) {
ForEach(elements) { element in
Text(element.localizedTitle)
}
}
.pickerStyle(.segmented)
}
}
}
With all of those you can create a view like this:
ModeSelectionView(elements: viewModel.modes, selectedElement: $viewModel.editScheduleMode)