Proper way to use custom AlignmentGuide - swiftui

So my problem is that I am trying to align text from different HStacks. Different sized SFSymbols are causing this problem.
I know that AlignmentGuide can solve my problem but I'm not sure how to implement. If anyone can lend any insight I would be greatly appreciative!
I have watched the WWDC talk on this.
And this is the main screen that relates to this problem,
Yet I need a little clarification of to put it together.
Here is relevant code
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("SOS Mayday!").bold()
.font(.largeTitle)
Rectangle()
.frame(height: 1)
HStack {
Image(systemName: "textformat.abc").imageScale(.large)
Text("Trying to figure out how to use alignment guide").bold()
}.padding(.vertical)
HStack {
//MARK:- FIX ALIGNMENT
Image(systemName: "aqi.low").imageScale(.large)
Text("This text should align with the text above").bold()
}
Spacer()
}.padding(.horizontal)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

I was able to produce the output you want using these steps:
Create a custom alignment enum based on AlignmentID and a
static instance of it as shown in the slide you linked.
Add a new VStack around just the part you want to custom align. Otherwise it can affect the alignment of the other components.
Add an alignmentGuide() on the two Text's that you want to align at their
leading edge.
Here is the updated code:
extension HorizontalAlignment {
private enum LeadingAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context[.leading]
}
}
static let leadingAlign = HorizontalAlignment(LeadingAlignment.self)
}
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("SOS Mayday!").bold()
.font(.largeTitle)
Rectangle()
.frame(height: 1)
// The new VStack using the custom alignment.
VStack(alignment: .leadingAlign) {
HStack {
Image(systemName: "textformat.abc").imageScale(.large)
Text("Trying to figure out how to use alignment guide")
.bold()
.alignmentGuide(.leadingAlign) { d in
d[.leading]
}
}.padding(.vertical)
HStack {
//MARK:- FIX ALIGNMENT
Image(systemName: "aqi.low").imageScale(.large)
Text("This text should align with the text above")
.bold()
.alignmentGuide(.leadingAlign) { d in
d[.leading]
}
}
Spacer()
}
}.padding(.horizontal)
}
}

Related

Prevent SwiftUI Divider expanding horizontally

I have the following setup:
struct ContentView: View {
var body: some View {
VStack {
Text("Title").font(.title)
HStack {
Text("Hello:").bold()
Text("World")
}
}
.padding()
.background {
RoundedRectangle(cornerRadius: 8).stroke(.black)
}
}
}
which renders as follows:
When I add a Divider() into the VStack…
struct ContentView: View {
var body: some View {
VStack {
Text("Title").font(.title)
Divider() // Added this
HStack {
Text("Hello:").bold()
Text("World")
}
}
.padding()
.background {
RoundedRectangle(cornerRadius: 8).stroke(.black)
}
.padding()
}
}
… it forces the VStack to expand horizontally as large as possible.
How do I make the Divider fit to the other VStack content?
Having written the question, I tried some more things, and found a solution. Leaving here in case it is useful for others.
I just needed to add
.fixedSize(horizontal: true, vertical: false)
to the VStack, which makes all the subviews have the same horizontal size, resulting in…

Why do the views extend wider than the screen?

Edit: Substitute your "system name:" of choice. "pencil.circle" works fine. "edit" is not a valid SF Symbol.
(I've simplified my code so you can cut and paste. That's why you see .frame, resizable, etc. where much simpler code might your first instinct.)
I have created a view which is a vertical list of row items (table view).
Each row item has a horizontal view with two images inside it.
The images take up too much space and do not fit correctly on the screen:
import SwiftUI
#main
struct StackOverflowDemoApp: App {
var body: some Scene {
WindowGroup {
TandemView()
}
}
}
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(height: 80)
.aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(width: 200, height: 80)
}
.padding()
.fixedSize()
}
}
struct TandemView_Previews: PreviewProvider {
static var previews: some View {
TandemView()
}
}
The above is the closest I can get to the desired layout (it just needs to fit horizontally). I experimented with GeometryReader but that did not produce desired results.
Here are some things I tried:
The code as provided
NoConstraintsOnPencilOrHStack
NoConstraintsOnTandemView
NoConstraintsOnImageInPaddedViewButWithFrameConstraint
I am trying to get a row view which consists of two Images (my actual source consists of UIImage objects) that fits within the width of the screen.
Edit:
After Accepting cedricbahirwe's spot-on response, I was able to simplify the code further. New results:
I added at the top level
TandemView()
.padding(.horizontal)
I removed:
// Spacer()
at the end of PaddedImageView
updated TandemView -- changed both frames and removed 3 lines:
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(width: 80, height: 80)
// .aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(height: 80)
}
// .padding()
// .fixedSize()
}
}
This is happening because of the layout of PaddedImageView View, you can actually remove the Spacer since it is not needed there.
So change
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
to
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
}
}
Note:
SwiftUI Engine infers the layout of your view from the implementation of the body property. It's recommended to have one Parent View inside the body property.

How To Ignore Insets In SwiftUI Form

I'm trying to ignore the automatic insets on a SwiftUI form in order to have an image fill the entire width of the screen. I've managed to do so by creating a fake section and adding negative padding, but this seems like a hack. Does anyone have a better suggestion?
struct ContentView: View {
var body: some View {
NavigationView{
Form{
Section(header:
Image("stockPhoto1")
.resizable()
.frame(height: 200)
.padding(.horizontal, -20)
.listRowInsets(.init())
){}
ForEach(0..<5){ section in
Section(header: Text("Section \(section)")){
ForEach(1..<Int.random(in: 3...8)){ row in
Text("Row \(row)")
}
}
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Form")
}
}
}

How to make this simple view with swiftui?

I am trying to do this view with swiftui but i am stuck.
I want the text("Mes évènements") to be centered and I want it to take all the place it can.
The two horizontal line should only take the place left.
I tried with HStack but I couldn't make it work as i would like to.
Here is a possible solution.
struct ContentView: View {
var body: some View {
HStack{
VStack{
OrangeLine()
}
Text("Mes évènements")
.font(.subheadline)
.fontWeight(.bold)
.foregroundColor(Color.orange)
VStack{
OrangeLine()
}
}
}
}
struct OrangeLine: View {
var body: some View {
Rectangle()
.fill(Color.orange)
.frame(height: 2)
.edgesIgnoringSafeArea(.horizontal)
}
}

Square Text using aspectRatio in SwiftUI

I'm trying to achieve a following layout using Swift UI…
struct ContentView : View {
var body: some View {
List(1...5) { index in
HStack {
HStack {
Text("Item number \(index)")
Spacer()
}.padding([.leading, .top, .bottom])
.background(Color.blue)
Text("i")
.font(.title)
.italic()
.padding()
.aspectRatio(1, contentMode: .fill)
.background(Color.pink)
}.background(Color.yellow)
}
}
}
I'd like the Text("i") to be square, but setting the .aspectRatio(1, contentMode: .fill) doesn't seem to do anything…
I could set the frame width and height of the text so it's square, but it seems that setting the aspect ratio should achieve what I want in a more dynamic way.
What am I missing?
I think this is what you're looking for:
List(1..<6) { index in
HStack {
HStack {
Text("Item number \(index)")
Spacer()
}
.padding([.leading, .top, .bottom])
.background(Color.blue)
Text("i")
.font(.title)
.italic()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fill)
.background(Color.pink)
.fixedSize(horizontal: true, vertical: false)
.padding(.leading, 6)
}
.padding(6)
.background(Color.yellow)
}
The answer being said, i don't recommend giving SwiftUI too much freedom to decide the sizings. one of the biggest SwiftUI problems right now is the way it decides how to fit the views into each other. if something goes not-so-good on SwiftUI's side, it can result in too many calls to the UIKit's sizeToFit method which can slowdown the app, or even crash it.
but, if you tried this solution in a few different situations and it worked, you can assume that in your case, giving SwiftUI the choice of deciding the sizings is not problematic.
The issue is due to used different fonts for left/right sides, so paddings generate different resulting area.
Here is possible solution. The idea is to give right side rect based on default view size of left side text (this gives ability to track dynamic fonts sizes as well, automatically).
Tested with Xcode 12 / iOS 14
struct ContentView: View {
#State private var height = CGFloat.zero
var body: some View {
List(1...5, id: \.self) { index in
HStack(spacing: 8) {
HStack {
Text("Item number \(index)")
Spacer()
}
.padding([.leading, .top, .bottom])
.background(GeometryReader {
Color.blue.preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
})
Text("i")
.italic()
.font(.title)
.frame(width: height, height: height)
.background(Color.pink)
}
.padding(8)
.background(Color.yellow)
.onPreferenceChange(ViewHeightKey.self) {
self.height = $0
}
}
}
}
struct ViewHeightKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
I managed to recreate the view in your first screenshot in SwiftUI. I wasn't sure on how much padding you wanted so I defined a private immutable variable for this value
The blue view is the one that will have the text content and could change in size so by using a GeometryReader you can get the size of the blue view and then use the height value from the size to set the width and height of the pink view. This means that whatever the height of the blue view is, the pink view will follow keeping an equal aspect ratio
The SizeGetter view below is used to get any views size using a GeometryReader and then binds that value back to a #State variable in the ContentView. Because the #State and #Binding property wrappers are being used, whenever the blueViewSize is updated SwiftUI will automatically refresh the view.
The SizeGetter view can be used for any view and is implemented using the .background() modifier as shown below
struct SizeGetter: View {
#Binding var size: CGSize;
var body: some View {
// Get the size of the view using a GeometryReader
GeometryReader { geometry in
Group { () -> AnyView in
// Get the size from the geometry
let size = geometry.frame(in: .global).size;
// If the size has changed, update the size on the main thread
// Checking if the size has changed stops an infinite layout loop
if (size != self.size) {
DispatchQueue.main.async {
self.size = size;
}
}
// Return an empty view
return AnyView(EmptyView());
}
}
}
}
struct ContentView: View {
private let padding: Length = 10;
#State private var blueViewSize: CGSize = .zero;
var body: some View {
List(1...5) { index in
// The yellow view
HStack(spacing: self.padding) {
// The blue view
HStack(spacing: 0) {
VStack(spacing: 0) {
Text("Item number \(index)")
.padding(self.padding);
}
Spacer();
}
.background(SizeGetter(size: self.$blueViewSize))
.background(Color.blue);
// The pink view
VStack(spacing: 0) {
Text("i")
.font(.title)
.italic();
}
.frame(
width: self.blueViewSize.height,
height: self.blueViewSize.height
)
.background(Color.pink);
}
.padding(self.padding)
.background(Color.yellow);
}
}
}
In my opinion it is better to set the background colour of a VStack or HStack instead of the Text view directly because you can then add more text and other views to the stack and not have to set the background colour for each one
I was searching very similar topic "Square Text in SwiftUI", came across your question and I think I've found quite simple approach to achieve your desired layout, using GeometryProxy to set width and heigh of the square view from offered geometry.size.
Checkout the code below, an example of TableCellView which can be used within List View context:
import SwiftUI
struct TableCellView: View {
var index: Int
var body: some View {
HStack {
HStack {
Text("Item number \(index)")
.padding([.top, .leading, .bottom])
Spacer()
}
.background(Color(.systemBlue))
.layoutPriority(1)
GeometryReader { geometry in
self.squareView(geometry: geometry)
}
.padding(.trailing)
}
.background(Color(.systemYellow))
.padding(.trailing)
}
func squareView(geometry: GeometryProxy) -> some View {
Text("i")
.frame(width: geometry.size.height, height: geometry.size.height)
.background(Color(.systemPink))
}
}