SwiftUI align SF Symbol as text with a text - image

I need to display a SF Symbol (image) and a text in a view (as a title). As the text could be quite long, I want that it passes under the image for second line.
Expected result (almost!):
I first tried a HStack with image and text. But text doesn't go under image: Second line starts with a left 'padding' (corresponding to left image space).
So I did this code (to get previous screenshot):
HStack(alignment:.top) {
Text(Image(systemName: "circle.hexagongrid.circle"))
.font(.caption)
.foregroundColor(.pink)
+
Text("A long text as example")
.font(.title2).bold()
.foregroundColor(.pink)
}
.frame(width:140)
But I want to align image with center of first line. Center of image should be vertically in middle of top of A and bottom of g.
It is important that my second (and more) line passes under my image. And I don't want a bigger font for image.

I don’t know if you can force the image to automatically be centred in all cases, but you can add a .baselineOffset() modifier to the image-in-text to shift the image upwards by a fixed amount. For example:
Text(Image(systemName: "circle.hexagongrid.circle"))
.font(.caption)
.foregroundColor(.pink)
.baselineOffset(3.0)
+
Text("A long text as example")
.font(.title2).bold()
.foregroundColor(.pink)
Once you have hit upon the right offset amount for the default text size, you might be able to accommodate accessibility variations by making the offset size relative to a base size, e.g.
Struct MyView: View {
#ScaledMetric(relativeTo: .title2) var baselineOffset: CGFloat = 3.0
// and in your body…
.baselineOffset(baselineOffset)

Firstly, you can remove the HStack because it is redundant. You can just replace it with brackets () to encapsulate the new Text, so you can use more modifiers like for setting the width.
The main solution is to use baselineOffset(_:) which means you can offset your image from the baseline of Text. Below, we only offset by 1, but this can be any value you want (including decimal numbers, e.g. 0.99).
Code:
struct ContentView: View {
var body: some View {
(
Text(Image(systemName: "circle.hexagongrid.circle"))
.font(.caption)
.foregroundColor(.pink)
.baselineOffset(1)
+
Text("A long text as example")
.font(.title2)
.bold()
.foregroundColor(.pink)
)
.frame(width: 140)
}
}
Result:
Bonus: you can move your foregroundColor(.pink) modifier after the Texts have been combined, so you don't have to duplicate that.

Have you tried embedding Image in Text?
Text("Super star \(Image(systemName: "star"))")

Related

How can justify text in Swift UI?

I am new to Swift UI. I have been using storyboard all this while. I am stuck at a place in swiftUI. Could one of you please explain how to justify Text in swiftUI? I referred to few links but they were of not much help.
SwiftUI: Justify Text
Kindly help! I am stuck at this very badly
An important thing to remember about swift is that views are typically arranged in stacks, mostly V and H stacks. You can use those stacks to move text around as you want. Now a common issue you might run into would be a stack that is not as wide as you'd expect, which you can fix with modifiers such as minimumFontScale and others. For the sake of this explanation I'll show you common structures for aligning text as you want.
Horizontal Alignment, Center of View
VStack {
Spacer() // Disable for Top Left/Right
HStack {
Spacer() //Disable for Left Align
Text("Test")
Spacer() //Disable for Right Align
}
Spacer() // Disable for Bottom Left/Right
}
Notice the VStack has two spacers to center the nested HStack. Then the HStack has spacers to align to the left of the view or the right.
Vertical Alignment, Center of View
HStack {
Spacer() //Disable for Left, Top/Bottom
VStack {
Spacer() //Disable for Top Align
Text("Test")
Spacer() //Disable for Bottom Align
}
Spacer() //Disable for Right, Top/Bottom
}
This one places the text in the middle and allows you to move it up and down. You should be able to look at these structures and see how you can manipulate it to move your views around.
Other Ways of Manipulating Alignment
VStack(alignment: .leading) {} //Leading and Trailing alignments.
HStack(alignment: .top) {} //Top and Bottom alignments.
//A Modifier directly on the view, where you could get the width
//and height from anything you want. Alignments could be.
// .leading, .trailing, .bottom, .top, .topleading, .topTrailing
// .bottomLeading, .bottomTrailing
Text("YourText").frame(width: 300, height: 300, alignment: .topLeading)
Using these types of modifiers can set your views up so that they can be cleaner than the above solutions, and become particularly useful when dealing with complex UI's that might have groups/sections. Nested stacks can have their modifiers in ADDITION to their parent stacks modifiers. The best way to find a solution for you might be different for someone else's UI. Play around with all the different ways of modifying alignment. It will come naturally over time, I'd say in a week or so.
TIP
Text views only take up the required amount of space in a stack. Stacks will only be as WIDE or as HIGH as their largest element on that axis. For example a Text("A") and Text("ABCDEFG") inside of a VStack will have the width of the second one, and a height of both. When working with views you should keep that in mind. There are ways to set stacks to different widths/heights using GeometryReader and .frame(...) modifier. I'll omit those for this answer.

SwiftUI: fit SF Icon into line height

When I try to display an icon next to a text, the Image adds unwanted space above, increasing the line height and I don't understand how I can control it. I tried frame, padding, resizable without success. I also tried the Label element, which my XCode does not recognize ("unresolved identifier Label").
The image shows the difference between an Image and Text, where the Image adds unwanted space.
Where is that extra space coming from and how can I control it?
VStack {
Text("User Name").bold()
HStack {
Text("hello#contact.com")
Image(systemName: "checkmark.seal.fill")
.font(.system(size: 14))
.foregroundColor(.blue)
}
}
The image shows the difference between an Image and Text, where the Image adds unwanted space.
Where is that extra space coming from and how can I control it?
It is all about default spacing... it is, yes, strange, use explicit, eg
VStack(spacing: 0) {
// ... other your code
}

Display animated SVG file in SwiftUI

I am trying to put in animated .svg images in a WatchKit SwiftUI project.
Unfortunately I could neither find a way to convert the svg to png "frames", nor a way to directly use the svg files as an animation.
Is there a way to use animated .svg images in SwiftUI?
I don't think .svg is natively supported. Possible workarounds:
A WebView should be able to load it.
Convert to .pdf/.png/.jpeg
https://github.com/SVGKit/SVGKit
Not sure if there is a very simple solution for WatchKit.
However, WatchKit has a WKInterfaceImage.animatedImage(with:duration:) that lets an ImageView animate through a set of images.
you still have to convert the frames of your SVG to PDF/PNG/JPEG.
This might be a good starting point for you: https://developer.apple.com/documentation/watchkit/wkinterfaceimage#1652345
I have found a solution to my dilemma, however it is far from optimal:
1. Create HTML page to display the animated svg
<object data="file.svg" type="image/svg+xml"></object>
You should set the page's background color to the background color you want to use where you need the animated image. I set it to black.
2. Creating a png sequence
Using the tool ScreenToGif (screentogif.com) I recorded the html file inside chrome with maximum zoom. The tool allows you to export to many different formats, one of them being a sequence of independent "frames" as single png files.
3. XCode: Create a Timer and image array
var animatedIcons: [UIImage]! = (0...9).map { UIImage(named: "imageName\($0)")! }
#State var index: Int = 0
let timer = Timer.publish(every: 0.2, on: .main, in: .default).autoconnect()
4. Let the Image receive timer updates
Image(uiImage: self.animatedIcons[index])
.resizable()
.frame(width: 50, height: 50, alignment: .center)
.onReceive(timer) { (_) in
self.index = self.index + 1
if self.index > self.animatedrIcons.count - 1 {
self.index = 0
}

How to scroll to line in QML TextArea?

How can I scroll to a particular line in a multiline, rich text QML TextArea? There is positionToRectangle but that only accepts a position as an int which doesn't seem suitable for multiline text.
I suggest trying to calculate the position based on the line number multiplied by line height. You can calculate height of one line like this:
(textArea.implicitHeight - 2 * textArea.textMargin) / textArea.lineCount
This might help you to use positionToRectangle.
edit: as an afterthought, have you tried setting cursorPosition?
A TextArea is not scrollable by itself (cf Scrollable TextArea):
If you want to make a TextArea scrollable, [...] it can be placed inside a ScrollView.
Then you just have to set the contentY of your contentItem:
ScrollView {
id: view
width: parent.width
height: 60
TextArea {
text: "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL"
}
}
Button {
text: "scroll"
onClicked: view.contentItem.contentY += 10
}

Horizontal scrollView/UIImageView layout issue

The goal: Have a scroll view that displays an array of uiimageviews (photos) that you can horizontally scroll through them
How I understand to do this: Make the frame (CGRect) of each uiimageview the height and width of the scroll view, the y value to 0 on each, and set the first imgViews x value to 0. For every imgView after that, add the width of the scrollview to the x value. In theory, this would line the imgViews (Photos) up next to each other horizontally and not allow for any vertical scrolling or zooming, purely a horizontal photo viewer.
The storyboard setup: I am creating my scrollview in a xib file (It’s a custom uiCollectionViewCell), with these constraints:
— Top space to cell (0)
— Trailing space to cell (0)
— Leading space to cell (0)
— Height of 400
— Bottom space to a view (0)
—— (See Below for img)
Laying out the UIImgViews:
func layoutScrollView() {
for (index, img) in currentImages.enumerate() {
let imgView = UIImageView(frame: CGRect(x: CGFloat(index) * scrollView.bounds.width, y: CGFloat(0), width: scrollView.bounds.width, height: scrollView.bounds.height))
imgView.contentMode = .ScaleAspectFill
imgView.image = img
scrollView.addSubview(imgView)
scrollView.contentSize = CGSize(width: imgView.frame.width * CGFloat(index), height: scrollView.bounds.height)
scrollView.setNeedsLayout()
}
}
My suspicion: I suspect the issue is stemming from the auto layout constraints i’ve specified, but (considering Im asking a SO question) not sure
If there is a better way to do this (really the correct way) please let me know! I have been trying to wrap my head around this for a few days now.
I appreciate all responses! Thanks for reading
EDIT #1
I tried paulvs approach of setting setNeedsLayout & layoutIfNeeded before the "for" loop, and still no luck. Here is (out of three images selected) the second photo displaying. It seems that both the first and second photos are way longer than the content view and that would move the middle view over (Squished).
Your code looks fine except for a few details (that may be causing the problem):
Add:
view.setNeedsLayout()
view.layoutIfNeeded()
before accessing the scrollView's frame (a good place would be before the for-loop).
This is because when using Autolayout, if you access a view's frame before the layout engine has performed a pass, you will get incorrect frames sizes/positions.
Remove these lines from inside the for-loop:
scrollView.contentSize = CGSize(width: imgView.frame.width * CGFloat(index), height: scrollView.bounds.height)
scrollView.setNeedsLayout()
and place this line after (outside) the for loop:
scrollView.contentSize = CGSize(width: imgView.frame.width * CGFloat(currentImages.count), height: scrollView.bounds.height)

Resources