SwiftUI: how to remove spacing around Text()? - swiftui

As example I have 3 properties:
var path1FilePath:String = "Src/"
var path2FileName: String = "filename"
var path3Extension: String = ".jpg"
I need to display them with the following way:
HStack {
Text(status.path1FilePath)
Text(status.path2FileName).bold()
Text(status.path3Extension)
}
problem is spacing between Text() views. How to remove them?

SwiftUI allows us to combine strings together like Text("Hello ") + Text("World!"), so you can do the same here:
Text(path1FilePath)
+ Text(path2FileName)
+ Text(path3Extension)
Alternatively, if you still want or need to use an HStack, just use HStack(spacing: 0) and you'll get the same result.

There are 2 ways:
Solution 1:
Text(path1FilePath)
+ Text(path2FileName)
+ Text(path3Extension)
but in this way you cannot apply modifiers =(
Solution 2:
HStack (spacing: 0) {
Text(path1FilePath)
Text(path2FileName)
.bold()
Text(path3Extension)
}
.someTextModifier()

Related

How to add a NavigationLink inline between 2 Text

I want to have a NavigationLink in between 2 Text elements in SwiftUI. Concatenating them together isn't working since Swift apparently only accepts concatenating elements of the same type.
Example
Expected outcome: Read our Terms & Conditions to learn more.
What I've tried:
Text("Read our ") + NavigationLink("Terms & Conditions") { TermsPage() } + Text(" to learn more.")
The error I got
Cannot convert value of type 'NavigationLink<Label, TermsPage>' to expected argument type 'Text'
I know that you can embed links in Markdown format
Text("Read our [Terms & Conditions](example.com/terms) to learn more.")
But that will open a webpage which isn't the desired behavior. I have a page inside the app that I want to navigate into. I just need the link to be inline with the text.
you could try something simple like this approach using a HStack:
#State var showTerms = false
//....
NavigationStack {
HStack {
Text("Read our")
Text("Terms & Conditions").foregroundColor(.blue)
.onTapGesture { showTerms = true }
Text("to learn more.")
}.fixedSize()
NavigationLink("", destination: TermsPage(), isActive: $showTerms)
}

Wrapping multiline text inside a shape in SwiftUI

As the title states, I am looking for a way to wrap multiline text inside of a shape in SwiftUI (see image below). I tried using .clipToShape(MyShape()), but that just renders any text not within the shape invisible.
I have accomplished this in the past using UIKit and exclusion paths, but would like to find a way to achieve the same effect with SwiftUI.
Any advice would be greatly appreciated.
I found a numeric way to do so, but it is probably not the best one.
It should work on IOS and macOS. I tested it on macOS with swiftUI.
The first thing we do is to find out how many rectangles with the hight of the font size are fitting in the circle with its diameter. Then we figure their width out. The last step is grabbing for each rectangle the amount of characters fitting in and adding them into an array. By converting the hole array back to a String we add after each rectangle an "\n" to get the correct multiline alignment.
func createCircularText(text: String, verticalSpacing: Double, circleDiameter: Double, FontSize: Double) -> String {
var Text = text
var circularText = String()
var CountOfWordLines = Int()
var widthOfWordLine = [Int]()
var widthOfWordLineSorted = [Int]()
var array = [String]()
let heigthOfWordLines = FontSize + verticalSpacing
var Dnum = (((1/heigthOfWordLines) * circleDiameter) - 2.0)
Dnum.round(.up)
CountOfWordLines = Int(Dnum)
for n in 1...(CountOfWordLines / 2) {
let num0 = circleDiameter / 2.0
let num1 = pow(num0, 2.0)
let num2 = (Double(n) * heigthOfWordLines)
let num3 = pow(num2,2.0)
let num4 = num1 - num3
let num5 = sqrt(Double(num4))
let num = Int((num5 / 10) * 3)
widthOfWordLine.append(Int(num))
}
widthOfWordLineSorted.append(contentsOf: widthOfWordLine.sorted { $1 > $0 })
widthOfWordLine.removeFirst()
widthOfWordLineSorted.append(contentsOf: widthOfWordLine)
widthOfWordLine.removeAll()
for n in widthOfWordLineSorted {
array.append(String(Text.prefix(n)))
if Text.isEmpty {} else {
let t = Text.dropFirst(n)
Text = String(t)
}
}
circularText = array.joined(separator: "\n")
return circularText
}
In our view we embed the function like this:
#State var text = "your text"
#State var CircularText = String()
// body:
ZStack {
Circle().frame(width: 200)
Text(CircularText)
.multilineTextAlignment(.center)
}
.onAppear(perform: {
CircularText = createCircularText(text: text, verticalSpacing: 3.0, circleDiameter: 200, FontSize: 12)
})
I just tested it with the font size 12, but it should perform with any other as well quite ok.
By changing the diameter you will notice that the text becomes a bit oval, to fix that please change the verticalSpacing. As smaller the number gets, as taller the circle gets, and the other way around. But feel free to fix that issue.
Also, please make sure that your text is long enough.

Smart separators between self-hiding views

I have a view that encapsulates some presentation logic, and as part of that logic, it can hide itself. A toy example:
struct Item: View {
var x: Int
var body: some View {
if x % 3 == 1 {
return AnyView(EmptyView())
}
return AnyView(Text("\(x)").background(Color.blue))
}
}
When I'm using my Item's in a VStack, it is smart enough to insert spacing only between non-empty ones.
VStack(spacing: 8) {
Item(x: 0)
Item(x: 1)
Item(x: 2)
Item(x: 3)
}
Now I want to do the same, but using a custom separator instead of spacing. Similarly, I want separator to be inserted only between non-empty items.
Is there an API that would insert 2 separators between 3 visible views? Something like this:
Something(separator: Divider()) {
Item(x: 0)
Item(x: 1)
Item(x: 2)
Item(x: 3)
}
I've checked VStack, Group, ForEach but didn't find anything. I really don't want to lift the hiding logic up into the parent. Any ideas for the workaround that would keep the hiding logic inside the Item?
Here is possible approach (and you continue use same VStack)
Tested with Xcode 12 / iOS 14
struct Item: View {
var x: Int
var body: some View {
if x % 3 == 1 {
return AnyView(EmptyView())
}
return AnyView(
VStack {
Text("\(x)").background(Color.blue)
Divider().padding(.horizontal)
}
)
}
}

Is it possible to set a character limit on a TextField using SwiftUI?

[RESOLVED]
I am using a codable struct which stores the object values retrieved from an API call so I have amended my TextField using Cenk Belgin's example, I've also removed extra bits I've added in so if anyone else is trying to do the same thing then they won't have pieces of code from my app that aren't required.
TextField("Product Code", text: $item.ProdCode)
.onReceive(item.ProdCode.publisher.collect()) {
self.item.ProdCode = String($0.prefix(5))
}
Here is one way, not sure if it was mentioned in the other examples you gave:
#State var text = ""
var body: some View {
TextField("text", text: $text)
.onReceive(text.publisher.collect()) {
self.text = String($0.prefix(5))
}
}
The text.publisher will publish each character as it is typed. Collect them into an array and then just take the prefix.
From iOS 14 you can add onChange modifier to the TextField and use it like so :
TextField("Some Placeholder", text: self.$someValue)
.onChange(of: self.someValue, perform: { value in
if value.count > 10 {
self.someValue = String(value.prefix(10))
}
})
Works fine for me.
You can also do it in the Textfield binding directly:
TextField("Text", text: Binding(get: {item.ProCode}, set: {item.ProCode = $0.prefix(5).trimmingCharacters(in: .whitespacesAndNewlines)}))

How to create tappable url/phone number in SwiftUI

I would like to display a phone number in a SwiftUI Text (or any View), and then make it clickable so that it will open the 'Phone'.
Is there a way to do this with SwiftUI, or should I try to wrap a UITextView in SwiftUI and do it the old-fashioned way with NSAttributed string etc?
I've read the documentation for Text in SwiftUI, and couldn't find anything about how to do this. Currently trying to do this in Xcode 11 beta 5.
I've searched 'text' in the SwiftUI API in SwiftUI.h
I've also searched stackoverflow [swiftui] and google with queries like "make phone number/url tappable", "Tappable link/url swiftUI" etc..
Text("123-456-7890")
.onTapGesture {
// do something here
}
(text will be Japanese phone number)
Using iOS 14 / Xcode 12.0 beta 5
Use new link feature in SwiftUI for phone and email links.
// Link that will open Safari on iOS devices
Link("Apple", destination: URL(string: "https://www.apple.com")!)
// Clickable telphone number
Link("(800)555-1212", destination: URL(string: "tel:8005551212")!)
// Clickable Email Address
Link("apple#me.com", destination: URL(string: "mailto:apple#me.com")!)
Try this,
let strNumber = "123-456-7890"
Button(action: {
let tel = "tel://"
let formattedString = tel + strNumber
guard let url = URL(string: formattedString) else { return }
UIApplication.shared.open(url)
}) {
Text("123-456-7890")
}
Thanks to Ashish's answer, I found the necessary code I needed to solve this:
In the action inside of the Button - you need to call this method:
UIApplication.shared.open(url)
to actually make the phone call / open a link in a SwiftUI View.
Of course, I didn't understand how to format my phone number at first, which I found in these answers:
How to use openURL for making a phone call in Swift?
Don't forget to add the 'tel://' to the beginning of your string/format it as URL..
The full code of what worked is
Button(action: {
// validation of phone number not included
let dash = CharacterSet(charactersIn: "-")
let cleanString =
hotel.phoneNumber!.trimmingCharacters(in: dash)
let tel = "tel://"
var formattedString = tel + cleanString
let url: NSURL = URL(string: formattedString)! as NSURL
UIApplication.shared.open(url as URL)
}) {
Text(verbatim: hotel.phoneNumber!)
}
KISS answer:
Button("url") {UIApplication.shared.open(URL(string: "https://google.com")!)}
From iOS 14, Apple provides us a Link view by default. So, you can just use this,
Link("Anyone can learn Swift", destination: URL(string: "https://ohmyswift.com")!)
For the previous versions of iOS, like iOS 13.0, you still have to use
Button("Anyone can learn Swift") {
UIApplication.shared.open(URL(string: "https://ohmyswift.com")!)
}
iOS 15 (beta)
Take advantage of Markdown in SwiftUI, which supports links!
struct ContentView: View {
var body: some View {
Text("Call [123-456-7890](tel:1234567890)")
}
}
Result:
Make it a Button() with an action, not a Text() with a gesture.
Button {
var cleanNum = selectedItem.phoneNum
let charsToRemove: Set<Character> = [" ", "(", ")", "-"] // "+" can stay
cleanNum.removeAll(where: { charsToRemove.contains($0) })
guard let phoneURL = URL(string: "tel://\(cleanNum)") else { return }
UIApplication.shared.open(phoneURL, options: [:], completionHandler: nil)
} label: {
// ...
}
Using iOS 14 / Xcode 12.5
I add this in case you wants to use a view.
To make a call by tapping a View
Link(destination: URL(string: "tel:1234567890")!, label: {
SomeView()
})
To make a call by tapping a String
Link("1234567890", destination: URL(string: "tel:1234567890")!)
You can do this way as well if in case you need to log any event on tap of the link :
struct exampleView: View {
#SwiftUI.Environment(\.openURL) private var openURL
var body: some View {
Button {
if let url = URL(string: "https://www.google.com") {
openURL(url)
}
} label: {
Text("Link")
.foregroundColor(Color("EyrusDarkBlue"))
}
}
}