SwiftUI: How-to append view to ending of multiline text? - swiftui

Current
I have something like the following:
Short text with badge:
It's done simple with HStack atm:
HStack {
Text("Lorem ipsum dolor sit amet.")
.font(.title2)
Text("foo")
.font(.system(size: 14, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(Color.red)
.cornerRadius(5)
}
My Problem is that the main text is multiline and if it wraps, it looks like this:
Larger multiline text with badge:
Goal
But my goal is that the foo badge is always displayed after the last word of the main text, which should look like this:
Goal: Solution like Text + Text:
In other words I want the same behaviour as if I would use the concatenating feature of SwiftUI's Text (Text() + Text()), but I can't use that because of the formatting of the badge. The formatting modifiers like background() will change the return type to View and then the + operator doesn't work anymore.
What is the most elegant solution to achieve my goal? I would prefer not using UIKit.

This below solution gives the result but, the badge will be at the end of the line.
If, it is static text, you can use offset(x: ) to achieve that manually.
ZStack(alignment: .bottomTrailing) {
Text("But my goal is that the foo badge is always displayed after the last word of the main text, which should look like this:")
.font(.title2)
Text("foo")
.font(.system(size: 14, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(Color.red)
.cornerRadius(5)
.alignmentGuide(.bottom) { d in d[.bottom] }
// .offset(x: 50)
}

Related

How to get rid of one placeholder at a time when the user types on that line? (swiftUI)

I put "placeholders" in my TextEditor. I used .isEmpty but I soon learned that by typing in the TextEditor it gets rid of all placeholders. I only want it to get rid of the appropriate "placeholder" when the user types on that line but not get rid of them all at once. How might I achieve this?
I am starting to think .isEmpty is not the solution for this, but I'm not sure. Ideally I'd like to add a TextField INTO the TextEditor but I can't figure out how to do this.
Here is my .isEmpty code which gets rid of both "placeholders" when you type (or even press the space button):
ZStack (alignment: .leading){
if test.isEmpty{
VStack{
Text("Recipe Name")
.padding(.top,10)
.opacity(1)
.font(.system(size: 20))
.foregroundColor(.black)
Spacer()
}
}
if test2.isEmpty{
VStack{
Text("Recipe made by (link if applicable)")
.padding(.top, 50)
.opacity(1)
.font(.system(size: 20))
.foregroundColor(.black)
Spacer()
}
}
VStack{
TextEditor(text: $note.text)
.opacity(note.text.isEmpty ? 0.85 : 1)
.font(.custom("SanFrancisco", fixedSize: 20))
.onReceive(note.publisher(for: \.text), perform: setName)
.onReceive(
note.publisher(for: \.text)
.debounce(for: 0.5, scheduler: RunLoop.main)
.removeDuplicates()
){ _ in
try? PersistenceController.shared.saveContext()
}
.navigationTitle(note.name)
}
}
}

How to make whole List row a NavigationLink in SwiftUI

I am making a list of questions sets and want the whole row to navigate to next view (every row has a different destination view), but only the area next to the text is tappable. I tried creating a SetRow() outside of List and it was working completely fine, but when its inside of List only the empty area is working.
Red is the working area
List code here:
NavigationView {
VStack {
SearchBar(input: $searchInput)
List(viewModel.filterList(by: searchInput)) { setVM in
NavigationLink(destination:
SetView(viewModel: QuestionListViewModel(
emoji: setVM.questionSet.emoji,
title: setVM.questionSet.title,
setID: setVM.questionSet.id)
))
{
SetRow(viewModel: setVM)
}
.onLongPressGesture(minimumDuration: 1) {
selectedSetVM = setVM
showDeleteAlert.toggle()
}
}
.listStyle(PlainListStyle())
}
and SetRow code here:
HStack {
Text(viewModel.questionSet.emoji)
.font(.system(size: 50))
VStack(alignment: .leading) {
Text(viewModel.questionSet.title)
.font(.system(size: 20, weight: .semibold, design: .default))
VStack(alignment: .leading) {
Text("Questions: \(viewModel.questionSet.size)")
.font(.system(size: 15, weight: .light, design: .default))
.foregroundColor(.gray)
Text("Updated: \(viewModel.questionSet.lastUpdated, formatter: taskDateFormat)")
.font(.system(size: 15, weight: .light, design: .default))
.foregroundColor(.gray)
}
}
Spacer()
}
.padding(.top, 10)
The problem comes with your 'longPressGesture' which wants to control the content of your row and listen for longPresses. To avoid this you can use 'onTapGesture' to control the activation of the NavigationLink and 'onLongPressGesture' to activate your actionSheet or Alert.
Here is a short code example that demonstrates the usage:
https://stackoverflow.com/a/67099257

How can I horizontally align two text views so that the leading and trailing edges meet in the middle?

I have two Text views. They are placed next to each other horizontally. How can I align them such that the meeting point of the views is also the center of the container view, regardless of how long either string is?
For example...
This is the first string|Second string.
The pipe here would be the center of the container view. Obviously a simple HStack wouldn't work unless both strings were exactly the same width. (Side note: In my particular use case, the strings won't be so long that they'll need to truncate or line wrap, but that might be useful for other people who have this question).
You can use a .frame(maxWidth: .infinity) on both Texts, which will end up making them equal widths (50% of the parent).
struct ContentView : View {
var body: some View {
VStack {
HStack(spacing: 2) {
Text("Short")
.frame(maxWidth: .infinity, alignment: .trailing)
.border(Color.blue)
Text("Longer. This is longer text....")
.lineLimit(1) // remove/change this if you want it to accommodate multiple lines
.frame(maxWidth: .infinity, alignment: .leading)
.border(Color.green)
}
}
}
}
You can play with the alignment on each depending on your need. Of course, the borders are only there for debugging.
To ensure both sides of the pipe are equal width, you can use 2 Spacer()s.
HStack {
Spacer()
Divider()
Spacer()
}
Then, you can overlay text on top of each Spacer().
struct ContentView: View {
var body: some View {
HStack {
Spacer()
.overlay(
Text("Hi! This text is in multiple lines. Cool, right?")
.fixedSize(horizontal: false, vertical: true) /// allow text wrap
.multilineTextAlignment(.trailing) /// align text to right when there are multiple lines
.frame(maxWidth: .infinity, alignment: .trailing) /// align text to right when there's 1 line
)
Divider()
Spacer()
.overlay(
Text("Hello!")
.fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
)
}
}
}
Result:

Can't seem to utilize Spacer() between text() and textfield()

I can't seem to get the Spacer() function to work when I'm within an HStack and trying to create space between my Text and Textfield views. The Spacer works to space out other areas of the view, but whenever I attempt to space between these two elements, it doesn't work.
Here is the code I'm working with:
VStack(alignment: .leading, spacing: 0) {
Rectangle()
.frame(height: 2, alignment: .top)
.foregroundColor(Color("grey2"))
Text("Tickets")
.kerning(0.5)
.scaledFont(name: "Gotham Medium", size: 18)
.foregroundColor(Color("grey4"))
.padding(.horizontal)
.padding(.bottom, 3)
.padding(.top, 35)
Rectangle()
.frame(height: 2, alignment: .top)
.foregroundColor(Color("grey2"))
HStack {
Text("Ticket URL")
.kerning(0.5)
.scaledFont(name: "Gotham Book", size: 16)
.foregroundColor(Color("spaceblack"))
Spacer()
TextField("Enter URL", text: $url)
}
.background(Color.blue)
.padding()
Rectangle()
.frame(height: 2, alignment: .top)
.foregroundColor(Color("grey2"))
}
.frame(maxWidth: .infinity)
.background(Color.red)
try adding .fixedSize() modifier to your TextField
TextField("Enter URL", text: $url)
.fixedSize()
or set a frame like so
TextField("Enter URL", text: $url)
.frame(width:200, height:50, alignment:.leading)
The problem is that TextField and Spacer() would take all available space and in this case TextField gets the priority; However, if you specify a fixed size or a frame to it then TextField won't stretch to take full space instead it will be fixed.
.fixedSize would allow your TextField to start small but eventually it will keep stretching the more text you write which can cause unwanted behavior.
.frame will fix your size to the provided width and hence there won't be any stretch and Spacer will have priority to take available space.

How to have text in shapes in SwiftUI?

I want to add text (eg. Hi) in a shape (eg. Square) in SwiftUI and make them act as a single object.
It looks like there's no direct way to add text in shape in SwiftUI.
Here is what I consider to be a more comprehensive answer. This will work as of Xcode 11.5:
Text(question)
.fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(.center)
.padding()
.frame(width: 300, height: 200)
.background(Rectangle().fill(Color.white).shadow(radius: 3))
Notes:
fixedSize() will let the text wrap (since .lineLimit(nil) no longer is working). You can omit it if you simply want one line of text with ellipsis
multilineTextAlignment() allows you to center or align the text in any way
padding() gives the text more space to breathe within the Rectangle()
frame() sets the width and height of the Text() and hence, the Rectangle(), since it is the background of the Text()
background() sets the shape of the Text()'s background. I have added a fill color and a drop shadow here as well
The end result of this example is the text looks to appear within a cue card like shape!
Here is, IMO, most simple approach:
Generic schema
Text(_any_of_text_)
.background(_any_of_Shape)
eg:
Text("Hello, World!")
.background(Rectangle().stroke())
Text("Hello, World!")
.background(RoundedRectangle(cornerRadius: 4).stroke())
Using Swift built-in shapes such as Capsule(), RoundedRectangle() and etc. Then, you can apply .overlay to the shape. The overlay take in any view such as text.
Example:
var body: some View {
Capsule()
.fill(Color.blue)
.overlay(
Text("Hello World")
)
}
Outcome:
Text("Hi")
.frame(width: 40, height: 40, alignment: .center)
.background(Color.black)
.clipShape(Rectangle())
Create a new SwiftUI View and make use of a Z-Stack to create your goal.
struct YourCustomObject: View {
var body: some View {
ZStack {
Rectangle()
.fill(Color.secondary)
.frame(width: 200, height: 200)
Text("Your desired text")
.foregroundColor(.white)
}
}
}