SwiftUI: fit SF Icon into line height - image

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
}

Related

GraphQL Gatsby Plugin Image set max-width to size of original image

I am using Google Photos to host photos for a website I am managing, and accessing them via GraphQL and Gastby (gatsby-image-plugin)
The images are shown in a gallery, but open up in a light-box gallery slider - I'm using FancyApps/ui (v4.x). Anyway the maximum size of the images are the maximum size of the source set (i.e. 512px). This means on a big screen the full screen image looks small (only 512px wide). You can see these values on the screen-grab below:
"original": {
"width": 512,
"height": 341
}
The original image is 1200px width, which is confirmed by the media metadata:
"mediaMetadata": {
"height": "800",
"width": "1200"
}"
Which is the same as images > sources > sizes:
"sizes": "(min-width: 512px) 512px, 100vw"
I realise I can force the value by specifying gatsbyImageSharp to have a width of 1200.
{
allGooglePhotosAlbum(filter: {title: {eq: "assorted"}}) {
nodes {
title
photos {
file {
childImageSharp {
id
gatsbyImageData(placeholder: BLURRED, width: 1200)
original {
width
height
}
}
}
mediaMetadata {
height
width
}
}
}
}
}
However some of the images are not 1200px wide (i.e. the portrait images), I get the following warning:
The requested width "1200px" for a resolutions field for the file URL_HERE was larger than the actual image width of 512px! If possible, replace the current image with a larger one.
I don't like the warning, but more importantly I think this might make the height of the image too large to be displayed properly (i.e. either would be cropped or larger than the screen height - 100vh).
Surely there, should be a way to set the largest image width/height to the heights provided by the media metadata (i.e. the full un-adulterated image).
I don't think you need to play along with the width of the image rather than the layout display, otherwise you will face the issue you mention (some images has 1200 but not all of them, plus adding a scaling that may not fit all the images constraints).
That said, I think you can simply do something like:
{
allGooglePhotosAlbum(filter: {title: {eq: "assorted"}}) {
nodes {
title
photos {
file {
childImageSharp {
id
gatsbyImageData(placeholder: BLURRED, layout: FULL_WIDTH)
original {
width
height
}
}
}
mediaMetadata {
height
width
}
}
}
}
}
The specs about FULL_WIDTH:
Use this for images that are always displayed at the full width of the
screen, such as banners or hero images. Like the constrained layout,
this resizes to fit the container. However it is not restricted to a
maximum size, so will grow to fill the container however large it is,
maintaining its aspect ratio. It generates several smaller image sizes
for different screen breakpoints, so that the browser only needs to
load one large enough to fit the screen. You can pass a breakpoints
prop if you want to specify the sizes to use, though in most cases you
can allow it to use the default.
That way, the displayed images won't be constrained by their own constraints, but they will be by the container where they are displayed, allowing you to play around with CSS rules to customize them. In addition, the browser will only take the image that needs to fit the screen.
Just incase anyone else has this issue, I have managed to figure it out. It turns out that it wasn't related to the graphQL query at all, but rather with the gatsby-source-google-photos plugin I was using.
The this plugin has the option to set the maxHeight and maxWidth - these values are both 512px by default. Which was why that was the maximum image width I was seeing in my query was 512px (when the original image was 1200px). See the documentation
All I needed to do was to change these values to 1200px in my gatsby-config.js file and I got the larger images.

SwiftUI align SF Symbol as text with a text

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"))")

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.

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
}

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