Text() is ignoring multiple paragraphs from AttributedString(markdown: ...) - swiftui

Given the markdown string "**Line 1**\n\nLine 2" I expect an output of
Line 1
Line 2
Instead I get
Line 1Line 2
Surely this isn't a limitation of markdown or AttributedString. What am I missing?! How do I specify multiple paragraphs if not with two blank lines?
struct DemoView_Previews: PreviewProvider {
static var previews: some View {
Text(try! AttributedString(markdown: "**Line 1**\n\nLine 2"))
}
}

As discovered via the Apple Developer forums, .inlineOnlyPreservingWhitespace is needed:
Text(try! AttributedString(markdown: "**Line 1**\n\nLine 2",
options: AttributedString.MarkdownParsingOptions(interpretedSyntax:
.inlineOnlyPreservingWhitespace)))
And, of course, for those that may come along this answer later, it's worth mentioning that if you don't need to use AttributedString directly or aren't passing a variable to Text, you can use the string literal with markdown directly:
Text("**Line 1**\n\nLine 2")

Related

How to highlight links with Attributedstring? [duplicate]

Given the markdown string "**Line 1**\n\nLine 2" I expect an output of
Line 1
Line 2
Instead I get
Line 1Line 2
Surely this isn't a limitation of markdown or AttributedString. What am I missing?! How do I specify multiple paragraphs if not with two blank lines?
struct DemoView_Previews: PreviewProvider {
static var previews: some View {
Text(try! AttributedString(markdown: "**Line 1**\n\nLine 2"))
}
}
As discovered via the Apple Developer forums, .inlineOnlyPreservingWhitespace is needed:
Text(try! AttributedString(markdown: "**Line 1**\n\nLine 2",
options: AttributedString.MarkdownParsingOptions(interpretedSyntax:
.inlineOnlyPreservingWhitespace)))
And, of course, for those that may come along this answer later, it's worth mentioning that if you don't need to use AttributedString directly or aren't passing a variable to Text, you can use the string literal with markdown directly:
Text("**Line 1**\n\nLine 2")
Ok it is how it works
Text(try! AttributedString(markdown: "**Line 1**\nLine 2", options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)))
But if you load text from plist it doesn't work with placing there \n What you need to do is add Enter + Option

SwiftUI conditional modifier addition

Based on a bool, I would like to add one more modifier to a Text in SwiftUI.
Ideally, I would do something like that:
Text(text)
if(true) {
.bold()
}
.foregroundColor(Color.black)
.frame(alignment: .leading)
which throws errors - the only "uncomplicated" alternative I can think of is to, depending on the bool value, create 2 different Texts. However, this results in a lot of code duplication. What can I do instead?
I've also tried declaring the Text as a let variable to access it later in the code however this prevents the element from showing up.
What IS possible is the following setup:
let title = Text("text")
.foregroundColor(Color.black)
and then in the body do
if(true) {
title
.bold()
}
However, if I add one more modifier to the declaration, it tells me Property definition has inferred type 'some View', involving the 'some' return type of another declaration
Using conditional modifiers is not recommended by Apple, as it breaks the View's identity once the condition flips. An easy alternative for your usecase would be the ternary operator:
Text(text)
.fontWeight(condition ? .bold : .regular)
.foregroundColor(Color.black)
Ditto on what Florian S said, you should use a ternary as inline conditionals on view modifiers can lead to many issues, but... they can be useful sometimes as well, so if you want to use inline conditional operations on view modifiers do this.
Add an some extensions to view.. you don't need both of these but depending on how you want to to use it, each has their strengths
extension View {
#ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if (condition) {
transform(self)
} else {
self
}
}
#ViewBuilder func `ifInline`<Content: View>(_ condition: #autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
if condition() {
transform(self)
} else {
self
}
}
}
then, on the view you want to use the extension with do something like this
ForEach(values: self.multiDevice ? devices : device) { device in
Group {
ForEach(values: ColorScheme.allCases) { scheme in
self.viewToPreview
.background(Color.backgroundColor)
.colorScheme(scheme)
.previewDevice(PreviewDevice(rawValue: device))
.previewDisplayName("\(displayName) - \(scheme.previewName) - \(device)")
.if(self.wrapped) { view in
view.previewLayout(.sizeThatFits)
}
}
}
}
To use the second extension, the '.if' would turn into a '.ifInline'.
A small note, this use case is from a GenPreviews class I make in my projects to more easily show canvas previews on various devices and color schemes with a descriptive title I can provide a name for from the Provider and some bools I can pass to show either one device or multiple from two lists of options I include as well as wrapping or showing the view on a device preview.
The reason I bring this up is because this use case not only isn't used in production runtime, but isn't even included when compiling a release... which goes back to my first statement that I agree with Florian S. I have used inline conditionals on view modifiers for running code before, but it is not good practice and shouldn't be done unless circumstances require and permit it. A ternary operator for your situation would be the best approach.

What is the best way to demux a publisher's Output?

I have an ObservableObject with a few publishers:
private class ViewModel: ObservableObject {
#Published var top3: [SearchResult] = []
#Published var albums: [SearchResult.Album] = []
#Published var artists: [SearchResult.Artist] = []
}
The endpoint is a URLSessionDataPublisher that sends a single collection of values that can be either an album or an artist (there are actually more types but I'm reducing the problem set here.) What is the best way in Combine to separate this collection out into 3 collections: [Album], [Artist], and an array of 3 results that can be either Artist or Album?
DatabaseRequest.Search(for: searchTerm)
.publisher()
// now i want to separate the collection out into [Album] and [Artist] and assign to my 3 #Published vars
.receive(on: DispatchQueue.main)
.sink { }
.store(in: bag)
You are hitting a bit of a (common) fallacy that Combine is responsible for passing the changed data in SwiftUI. It isn't. The only thing Combine is doing here is providing the content-less message that some data has changed, and then the SwiftUI components that are using the relevant model object go and look for their data.
The data transfer in SwiftUI is entirely using Binding, which are essentially get and set closures under the covers.
So you don't really need to worry about demuxing a combine stream - and there isn't one that has "one" of these kinds of data in it. Combine would have trouble with that since it's strongly typed for both Output type and Failure type.
There's a bit more written about this in Using Combine under the chapter
SwiftUI and Combine (chapter link to the free HTML version)

SwiftUI ForEach does not compile with if block inside - Bug or am I doing something wrong?

I am getting an error that I don't understand. I am not sure if it is a compiler error or if I am doing something wrong?
Inside a swiftUI View I have a list showing elements from core data (Figure 1). In the example below I replaced the t.name with "yo" for some undefined reason 😅.
Anyway, the tasks is a fetch request from Core Data:
#FetchRequest(entity: Task.entity(), sortDescriptors: []) var tasks: FetchedResults<Task>
FIGURE 1: Works fine to build and run the app.
FIGURE 2: Does not work to build and run the app.
Please help me understand what I am doing wrong or is this a compiler bug? Why can't I add the if block inside the ForEach? I can provide more information if needed. Thanks!
You can use if inside ForEach, but you should remember that ForEach is not language operator foreach but a struct type with ViewBuilder in constructor with generics in declaration, so it needs to determine type, which in your case it cannot determine.
The possible solution is to tell explicitly which type you return, as below (tested with Xcode 11.2 / iOS 13.2)
ForEach(tasks, id: \.id) { name -> Text in
if (true) {
return Text("name")
}
}
You have to return nil in case of a false condition. So you need to declare parameter as Optional and return nil in case of a false condition (XCode - 11.3.1).
ForEach(tasks, id: \.id) { t -> Text? in
return condition ? Text("text") : nil
}
}
For some reason, in the ForEach.init you're using, the view building closure isn't annotated with #ViewBuilder. This means that the if/else you're using is Swift's own if statement, not the SwiftUI construct which returns a _ConditionalContent.
I don't know if it's considered a bug by Apple, but I would definitely consider it to be one.
The easiest workaround is just to wrap the if/else in a Group - the Group.init is a #ViewBuilder, so will handle the if/else correctly.
What also worked for me was using a Group inside the ForEach like this:
ForEach(self.items, id: \.self { item in
Group {
if item.id == 0 {
Text(String(item.id))
}
}
}

SwiftUI: Uppercase a localized string for a view, e.g. `Text`?

Using Xcode beta 6 and SwiftUI, creating a Text view using a >localized< string, how can I uppercase it without having to make the localized string value uppercased? I guess I want a ViewModifier for changing case, but it does not seem to exist?
Text("SignIn.Body.Instruction".uppercased()) will uppercase the localization key, so that will not work, as instructed in this SO question about uppercasing a non-localized string.
It is now possible with all new textCase ViewModifier (Xcode 12 Beta 3), like so:
Text("SignIn.Body.Instruction")
.textCase(.uppercase)
Or lowercase:
Text("SignIn.Body.Instruction")
.textCase(.lowercase)
How about using the smallCaps modifier for Font? E.g., Text("Hello World").font(Font.body.smallCaps()) will render "HELLO WORLD", but the underlying string will retain its proper localized capitalization for the purposes of accessibility.
Localise it manually and then uppercase it.
extension String {
func toUpperCase() -> String {
return localized.uppercased(with: .current)
}
private var localized : String {
return NSLocalizedString( self, comment:"")
}
}
Text("SignIn.Body.Instruction".toUpperCase())