SwiftUI conditional Text modifier .textSelection - swiftui

maybe a absolute beginner question:
In the following code is foregroundColor working but .textSelection doesn't. What is the reason?
Text("This is a Test")
.foregroundColor(isSelectable ? .green : .red)
.textSelection(isSelectable ? .enabled : .disabled)

We cannot put it ternary operator, because .enabled and .disabled are of different concrete types (confirming to one protocol), so possible variant is
let text = "This is a Test"
Group {
if isSelectable {
Text(text)
.textSelection(.enabled)
} else {
Text(text)
.textSelection(.disabled)
}
}
.foregroundColor(isSelectable ? .green : .red)
Note: actually Apple does not consider this feature as togglable, let's read the doc
/// A selectability value that enables text selection by a person using your app.
///
/// Enabling text selection allows people to perform actions on the text
/// content, such as copying and sharing. Enable text selection in views
/// where those operations are useful, such as copying unique IDs or
/// error messages. This allows people to paste the data into
/// emails or documents.
It's hardly imaginable that "useful informations electability" can be turned of for some reason. Just in case.

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 Link Long Text Alignment Multiline

I'm using SwiftUI's Link to open Safari from the application. But I have a long text for the link.
What is the problem
For now, the second line of the text always keeps aligned at the center.
What I want
I want to be able to use leading TextAlignment with it.
So I've tried to use multilineTextAlignment but didn't work.
Code
Link("Some long text even very looong even that long text here!", destination: URL(string: "https://www.apple.com/")!)
.multilineTextAlignment(.leading)
Need help.
Solution
My solution was using another signature of the Link itself with multilineTextAlignment.
Link(destination: URL(string: "https://www.apple.com/")!) {
Text("Some long text even very looong even that long text here!")
.multilineTextAlignment(.leading)
}
From Apple Documentation
public struct Link<Label> : View where Label : View {
/// Creates a control, consisting of a URL and a label, used to navigate
/// to the given URL.
///
/// - Parameters:
/// - destination: The URL for the link.
/// - label: A view that describes the destination of URL.
public init(destination: URL, #ViewBuilder label: () -> Label)
Hope will help someone else!
Best

How to combine two Text Views in SwiftUI that have different formatting

I can combine two text views like this.
Text("1") + Text("1")
But how can I do something like this?
Text("1") + Text("1").border(Color.red)
I cant simply do this
HStack(spacing: 0) {
Text("1")
Text("1").border(Color.red)
}
because the design will behave differently as seen below
They will behave as 2 separate texts. I want the one on the right to wrap around, so that both Texts looks like a single text.
operator + could be applied if both operands are the same type (Text in your case).
struct MyView: View {
var body: some View {
Text("A").font(.largeTitle).foregroundColor(Color.red) + Text("B").font(.title) + Text("C").font(.footnote)
}
}
gives you
which is exactly one Text again, just because .font or .foreground modifier could return Text if applied on Text.
.border modifier returns View, that is why it doesn't work in your example.

Qt/QML: Text with inline QML elements

We are building a graphical user interface with QtQuick/QML. We have some dynamic, multi-line text coming from a database, which should be displayed in the application. Currently, we are using the Text element to display the text. However, we need some QML components inline embedded into the text. For this, the text coming from the database contains placeholders such as ::checkbox|1:: which should then be replaced and displayed by the program.
In HTML, this is easy, you can simply mix inline elements with text to produce a result like this:
but in QML, this seems to be more difficult, as Text elements cannot be word-wrapped into two halves if there is not enough space (both the text and the container size should be dynamic).
The best solution we could come up with, is creating a Flow layout with one Text element for each word, but this seems too hacky.
Using RichText with HTML is not enogh, since we really need our custom QML elements in the text.
Also, we want to avoid using a WebView due to performance reasons.
Is there a sophisticated way to implement this with QML/C++ only?
You can create custom widgets and embed them into QML:
Writing QML Extensions with C++
I haven't tried placing something in the middle, but I did try adding a tag to the beginning (and I might try adding a tag at the end).
QML's Text has a lineLaidOut signal that let's you indent the first line of text.
http://doc.qt.io/qt-5/qml-qtquick-text.html#lineLaidOut-signal
Here's what I did:
Text {
text: issue.summary
onLineLaidOut: {
if (line.number == 0) {
var indent = tagRect.width + tagRect.rightMargin
line.x += indent
line.width -= indent
}
}
Rectangle {
id: tagRect
implicitWidth: padding + tagText.implicitWidth + padding
implicitHeight: padding + tagText.implicitHeight + padding
color: "#400"
property int padding: 2
property int rightMargin: 8
radius: 3
Text {
id: tagText
anchors.centerIn: parent
text: issue.product
color: "#fff"
}
}
}