Swiftui: multiple lines in a menu item or picker - swiftui

I've started using SwiftUI and trying to convert an application that I've created with Jetpack Compose to iOS, but I've run into a problem with menus (or picker if that's easier).
What I want to do is to be able to create items that has multi-lines and different font size for the two lines. But I can't make it work, only the first line is visible
I've tried something like:
Picker("Select menu item", selection: $selectedItem) {
ForEach(0..<items.count) {
VStack {
Text("Menu item 1").fontWeight(.bold) // Only this text is shown
Text("Description")
}
}
}
I've also tried with
Menu("Options") {
VStack {
Text("Menu item 1").fontWeight(.bold) // Only this text is shown
Text("Description")
}
}
What I want it to look like is something like:
I've spent many hours trying to find an example of something like this. Is this possible to do in SwiftUI? If not, what control should you suggest to use instead?

Related

how to match the font of an existing view

I'm curious, is there a way to make the fonts in a view match those of an existing view in SwiftUI? I don't like the default selections of swiftUI in a certain context, and I'd like some control over the situation.
Here's some code to illustrate:
struct FontMatchView: View {
var body: some View {
Form {
Section {
Text("Some Controls Here")
} header: {
HStack {
Text("Header")
Spacer()
Button("Option") {
}
}
}
}
}
}
This gives this result:
In the Section Header, I'd like the font in the button on the right (with label "OPTION") to match the label to its left ("HEADER"). I'm guessing this will be hard because the font is not known at the time of view definition. But the choices SwiftUI has made here are "clearly wrong" :-), and I need to fix this.
Is there a way we solve this (other than overriding both fonts)? Ideally, I could say "use a font that is 0.8 x the height of whatever font will be used in view X". But I'd settle for "use the same font as will be used in view X".
You can remove "buttonizing" (which includes adjusting the font) by applying .buttonStyle(.plain). This will make it match the other Text in the current context. If you then want to re-accent it, you may:
Button("Option") {}
.buttonStyle(.plain)
.foregroundColor(.accentColor)
That said (and somewhat unrelated), making the button as small as the HEADER text may make it uncomfortably small as a hit area. It may be better to make HEADER larger rather than OPTION smaller.

Swiftui list: trigger an action on tap and mimic basic select then auto-unselect

I'm starting to learn swiftui and I've run into a problem that is both very basic and easily solvable in UIKit; but after spending days searching the internet and watching WWDC videos I've found no native solution.
The premise is simple: I have an array of songs I want to display in a list; when a user taps on a song view it should highlight the view on press, unhighlight after release, and then play the song (ie trigger an action). Sounds simple right?
Here's what I tried and spent way too much time on:
Using List(selection) + .onEvent(changed): I end up with a UUID (because i've only gotten selection to work with a UUID) that I then have to check against an array of songs to match AND the cell won't unhighlight/select itself; even when I try to manually set the State variable to nil or another generated UUID.
Using .onTap (either on or in the cell): I have to tap on the text of the cell to trigger onTap so I get a lot of taps that just don't work (because I have lots of white space in the cell). I also don't get a nice UI color change on press/release.
So after spending hours trying many different things I've finally come up with a solution and I basically wanted to create an account and share it to hopefully help other developers in my position. Because this so very annoyed me that something so basic took so much effort and time to do.
In the end the best solution I came up with was this:
Using ZStack and an empty button:
edit: I found I need to include and hide the content otherwise the button doesn't grow to fill the space (seems in lists it does for some reason). Though not sure what the hit on performance is of rendering the content twice when hiding it. Maybe a GeometryReader would work better?
struct SelectionView: ViewModifier {
let onSelect: () -> Void
func body(content: Content) -> some View {
ZStack (alignment: .leading) {
Button {
onSelect()
} label: {
content
.hidden()
}
content
}
}
}
extension View {
func onSelection(_ selection: #escaping () -> Void) -> some View {
self.modifier(SelectionView(onSelect: selection))
}
}
then to use it:
SongCell(song: song)
.onSelection {
// Do whatever action you want
}
No messing around with list selection, no weird tap hit boxes, and get the press/release color change. Basically put an empty button in a ZStack and trigger off it's action. Could possibly cause tap/touch issues with more complicated cells (?) but it does exactly what I need it to do for my basic app. I'm just not sure why it took so much effort and why apple doesn't support such a basic use case by default? If I've overlooked something native please do inform me. Thanks.
I got the basic idea what you are trying to do. I'm Going to show simple example. Maybe using this you will be able to find proper solution.
First let's create a color : -
#State var colorToShow : Color = Color.blue
Now in body we have our ZStack or Your cell that we want to deal with : -
ZStack{
colorToShow
}.frame(width: 50, height: 50).padding()
.onLongPressGesture(minimumDuration: 3) {
print("Process Complete")
colorToShow = .green
} onPressingChanged: { pressing in
if pressing {
print("Pressing")
colorToShow = .red
} else {
print("Pressing Released")
colorToShow = .blue
}
}
Here we are using .onLongPressGesture. You can set minimum duration on which you want to perform action. Now on process completion You set what you want to do. OnPressingChange give you a bool value that changes according to user is pressing that button or not. Show color change(Highlight) or do action while bool value is true. When user release button do action or unhighlight since bool value turns false.
Hope you find it useful.

SwiftUI underline() results in broken underline

I'm trying to underline some text in a home screen widget but the underline always appears broken:
struct NotLoggedInSmallView: View {
var body: some View {
Text("Ready to write a new chapter in travel?")
.underline()
}
}
Changing font/sizae/baseline... doesn't help.
I worked around this in another places by just drawing line below the field but that doesn't handle wrapped text. Seems like pretty basic stuff but I can't seem to find a way that works.
Try attributed string instead
var body: some View {
Text(AttributedString("Ready to write a new chapter in travel?",
attributes: AttributeContainer([.underlineStyle: 1])))
}
Tested with Xcode 13.4 / watchOS 8.5

SwiftUI: append menu entry to group "View" after "Enter Full Screen"

I'd like to append a new menu entry right below "Enter Full Screen", but I am failing to find the right CommandGroupPlacement property.
CommandGroup(after: .<what needs to be put here??>) {
//my buttons here
}
Attempting to override "View" results in just another group with the same name (see image).
CommandMenu("View") {
//add button here
}
Shoutout to Majid Jabrayilov for his blogpost on this: https://swiftwithmajid.com/2020/11/24/commands-in-swiftui/
To find a solution to my above issue though I still had to think a bit around the corner–what does work is this:
CommandGroup(before: .toolbar) {
Button("Foo") {
}
}
This works, because the toolbar menu entry is located within "View" (even thought I don't have a toolbar in my app, the placement still works nonetheless...)
You said you were looking for the proper CommandGroupPlacement so for your case (relative to full screen mode), you would technically want CommandGroupPlacement.sidebar. CommandGroupPlacement.toolbar works because toolbar options live in the View menu, though you have none set.
/// Example in a CommandsBuilder
CommandGroup(after: CommandGroupPlacement.sidebar) {
Button("New View Action", action: { print("After sidebar menu options") }).keyboardShortcut("s", modifiers: [.command, .option, .control, .function]
}
This should also come in handy for others. The Apple documentation that corresponds to this: CommandGroupPlacement

SwiftUI TextField freezes when deleting first character

When specifying a minimumScaleFactor for a TextField in SwiftUI the TextField behaves normally while you enter text and reduces the font as specified when the content does not fit the TextView. However, if you start deleting characters everything works as usual until you delete the first character. Everything freezes.
At the beginning I though it was something in the way I was handling the variable that stores the text that in my application I have it as an ObservedObject. However, after debugging the frozen app I noticed that the code was circling around the drawing of the TextField over and over, function after function everything pointed to an error in the drawing of the object on the screen.
The following code illustrates the issue. The TextField works perfectly when you enter characters and delete them until you get to the first one. The it freezes.
import SwiftUI
struct ContentView: View {
#State var sensorNumber: String = ""
var body: some View {
TextField("WC0.000.000.000", text: $sensorNumber)
.padding(.all, 5.0)
.font(Font.custom("Helvetica", size:40.0))
.minimumScaleFactor(0.90)
}
}
The problem seems to be related to the interaction of the Custom Font. Obviously, my application is using custom fonts but here I just wanted to simplify the code.
This code does not fail if you don't use a custom font or if you don't specify a minimumScaleFactor. I have found a workaround that is not very elegant but it works until Apple fixes this bug:
import SwiftUI
struct ContentView: View {
#State var sensorNumber: String = ""
var body: some View {
TextField("WC0.000.000.000", text: $sensorNumber)
.padding(.all, 5.0)
.font(Font.custom("Helvetica", size:40.0))
.minimumScaleFactor(sensorNumber.count < 2 ? 1.0 : 0.90)
}
}
I am submitting a radar to Apple but looking for a better solution for the problem here.