I want to change the anchor point of the red line without affecting the whole layout.
So instead of having its horizontal anchor point .leading i want to be .center. So the center of the red line aligns with the leading of the black line.
VStack(alignment: .leading) {
Rectangle().fill(Color.Stock.gray).frame(height: 4)
.background(
GeometryReader { geo in
Rectangle().fill(Color.red).frame(width: 40, height: 8)
.alignmentGuide(HorizontalAlignment.leading) { dim in
dim[HorizontalAlignment.center]
}
},alignment: .leading)
}.padding(.horizontal, 32)
Result:
It want like this (Instead of adding it as a background i added to the VStack, but this modifies my layout to the right):
Updated: Xcode 13.4 / iOS 15.5
Simplified, no hardcode - only alignments:
FirstView()
.background(
SecondView()
.alignmentGuide(HorizontalAlignment.leading) { dim in
dim[HorizontalAlignment.center]
}
.alignmentGuide(VerticalAlignment.bottom) { dim in
dim[VerticalAlignment.top]
}
,alignment: .bottomLeading)
Test code is here
Original
Here is a demo of possible solution (with substituted color). Tested with Xcode 12 / iOS 14.
struct DemoView: View {
private let height = CGFloat(4)
var body: some View {
VStack(alignment: .leading) {
Rectangle().fill(Color.gray).frame(height: height)
.background(
VStack(alignment: .leading) {
Rectangle().fill(Color.red).frame(width: 40, height: 8)
.alignmentGuide(HorizontalAlignment.leading) { dim in
dim[HorizontalAlignment.center]
}
.alignmentGuide(VerticalAlignment.center) { dim in
dim[VerticalAlignment.top] - height / 2
}
},alignment: .leading)
}.padding(.horizontal, 32)
}
}
Related
I'm trying to create a chat bubble like this:
Actual Bubble
Actual Bubble 2.0
This is what I have been able to achieve so far.
My attempt
My attempt
This is my code so far:
import SwiftUI
struct TestingView: View {
var body: some View {
ZStack {
/// header
VStack(alignment: .trailing) {
HStack {
HStack() {
Text("abcd")
}
HStack {
Text("~abcd")
}
}.padding([.trailing, .leading], 15)
.fixedSize(horizontal: false, vertical: true)
/// text
HStack {
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
}.padding([.leading, .trailing], 15)
/// timestamp
HStack(alignment: .center) {
Text("12:00 PM")
}.padding(.trailing,15)
}.background(Color.gray)
.padding(.leading, 15)
.frame(maxWidth: 250, alignment: .leading)
}
}
}
struct TestingView_Previews: PreviewProvider {
static var previews: some View {
TestingView()
}
}
The main goal is that I want the two labels on top to be distant relative to the size of the message content. I am not able to separate the two labels far apart i.e one should be on the leading edge of the bubble and the other one on the trailing edge.
Already tried spacer, it pushes them to the very edge, we need to apart them relative to the content size of the message as shown in attached images.
Here is a simplified code.
Regarding Spacer: To achieve your desired result you put both Text views inside of a HStack, and put a Spacer between them. So the Spacer pushes them apart to the leading and trailing edge.
Also I recommend to only use one padding on the surrounding stack.
VStack(alignment: .leading) {
// header
HStack {
Text("+123456")
.bold()
Spacer() // Spacer here!
Text("~abcd")
}
.foregroundStyle(.secondary)
// text
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
.padding(.vertical, 5)
// timestamp
Text("12:00 PM")
.frame(maxWidth: .infinity, alignment: .trailing)
}
.padding()
.background(Color.gray.opacity(0.5))
.cornerRadius(16)
.frame(maxWidth: 250, alignment: .leading)
}
We can put that header into overlay of main text, so it will be always aligned by size of related view, and then it is safe to add spacer, `cause it do not push label wider than main text.
Tested with Xcode 13.4 / iOS 15.5
var body: some View {
let padding: CGFloat = 15
ZStack {
/// header
VStack(alignment: .trailing) {
/// text
HStack {
//Text("Hello Everyone") // short test
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!") // long test
}
.padding(.top, padding * 2)
.overlay(
HStack { // << here !!
HStack() {
Text("abcd")
}
Spacer()
HStack {
Text("~abcd")
}
}
, alignment: .top)
.padding([.trailing, .leading], padding)
/// timestamp
HStack(alignment: .center) {
Text("12:00 PM")
}.padding(.trailing, padding)
}.background(Color.gray)
.padding(.leading, padding)
.frame(maxWidth: 250, alignment: .leading)
}
}
To separate two components with fairly space in the middle, use HStack{} with Spacer().
This is a sample approach for this case. Code is below the image:
VStack {
HStack {
Text("+92 301 8226")
.foregroundColor(.red)
Spacer()
Text("~Usman")
.foregroundColor(.gray)
}
.padding(.bottom, 5)
.padding(.horizontal, 5)
Text("Testing testingtesting testing testing testingtesting testing testing testing testing testing testing testing testing testing.")
.padding(.horizontal, 5)
HStack {
Spacer()
Text("2:57 AM")
.foregroundColor(.gray)
.font(.subheadline)
}
.padding(.trailing, 5)
}
.frame(width: 300, height: 160)
.background(.white)
.cornerRadius(15)
Working on all things accessibility related and found that .accessibilityHidden(true) doesn't seem to work correctly. Even though VO doesn't read anything, it still shows the white box as you swipe around the page. The only way I've solved this is to put a VStack around the entire thing and then it seems to be fine, however it truncates the welcomeBodyText in simulator but not on a real device, which is worrying because I need to ensure that this works across all devices.
As the HeaderNav just shows the logo and company name, there is no reason to have it selectable, nor read out on every single page, so I wanted to completely hide it from VO and immediately jump to reading the welcomeHeaderText followed by the welcomeBodyText.
HeaderNav.swift
import SwiftUI
struct HeaderNav: View {
var body: some View {
VStack {
Spacer()
.frame(minHeight: 26, idealHeight: 26, maxHeight: 26)
.fixedSize()
HStack(spacing: 16) {
Spacer()
Image(decorative: "LogoHeader")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 70)
Text(LocalizationStrings.companyName)
.fontWeight(.light)
.foregroundColor(Color("WhiteText"))
.font(.system(size: 34))
.tracking(0.8)
Spacer()
} // End HStack
.accessibilityElement(children: .ignore)
.accessibilityLabel(LocalizationStrings.companyName)
} // End VStack
}
}
WelcomeTextView.swift
import SwiftUI
struct WelcomeTextView: View {
var body: some View {
Section {
VStack { // This is what needed to be added to make it work but causes issues in the simulator
HeaderNav()
.accessibilityHidden(true)
VStack(spacing: 10) {
Group {
HeaderText(text: LocalizationStrings.welcomeHeaderText)
BodyText(text: LocalizationStrings.welcomeBodyText)
} // End Group (Page Description)
.pageDescr()
} // End VStack
} // End VStack to make it work
.accessibilityElement(children: .combine)
} // End Section
.listRowBackground(Color("mainbkg"))
.listRowSeparator(.hidden)
}
}
pageDescr is a View Modifier that looks like:
struct PageDescr: ViewModifier {
func body(content: Content) -> some View {
VStack(alignment: .leading, spacing: 20) {
content
}
.padding(EdgeInsets(top: 5, leading: 32.0, bottom: 25, trailing: 32.0))
}
}
It was the top padding that was causing the issue, so I used a fixed size spacer in the HeaderNav above the closing } of the VStack instead and set the top padding to 0. Now it works.
Like this:
Spacer()
.frame(minHeight: 32, idealHeight: 32, maxHeight: 32)
.fixedSize()
I'm following a SwiftUI tutorial and have made this view that updates with data when a user drags or zooms into an image. After I detoured from the tutorial to round these strings to the nearest hundredth, I noticed this behavior where the text is moving back and forth (and kind of clipping for a second) when it's updating the values. I've tried various combinations of the .frame, lineLimit, minimumScaleFactor modifiers to no avail. The behavior I want is for the system icons to not move and the text to be left aligned against them and then the Text frames should take up all the available space left (and not clip into the text when the text goes from 4 characters long to 5 characters long)
Current Behavior:
InfoPanelView.swift:
...
struct InfoPanelView: View {
var scale: CGFloat
var offset: CGSize
#State private var isInfoPanelVisible: Bool = false
var body: some View {
HStack {
Image(systemName: "circle.circle")
.symbolRenderingMode(.hierarchical)
.resizable()
.frame(width: 30, height: 30)
.onLongPressGesture(minimumDuration: 1) {
withAnimation(.easeOut) {
isInfoPanelVisible.toggle()
}
}
Spacer()
HStack(spacing: 2) {
Image(systemName: "arrow.up.left.and.arrow.down.right")
Text(String(format: "%.2f", scale))
Spacer()
Image(systemName: "arrow.left.and.right")
Text(String(format: "%.2f", offset.width))
Spacer()
Image(systemName: "arrow.up.and.down")
Text(String(format: "%.2f", offset.height))
Spacer()
}
.font(.footnote)
.padding(8)
.background(.ultraThinMaterial)
.cornerRadius(8)
.frame(maxWidth: 420)
.opacity(isInfoPanelVisible ? 1 : 0)
Spacer()
}
}
}
...
I ended up creating a new subview:
struct ExpandingText: View {
var value: CGFloat
var body: some View {
ZStack {
Text(String(describing: (0..<500).map{letter in letter}))
.foregroundColor(Color.clear)
.lineLimit(1)
Text(String(format: "%.2f", value))
.id(value)
.transition(AnyTransition.opacity.animation(.easeInOut(duration:0)))
.multilineTextAlignment(.leading)
.lineLimit(1)
.frame(width: 75, alignment: .leading)
}
}
}
The 500 character invisible string that's limited to 1 line ensures that the ZStack takes up as much width as that text ever could (so the icons no longer move) and the id modifier combined with making the transition override duration being 0 fixes the text box clipping issue.
Edit:
Adding a larger width frame with alignment set to .leading as per xTwisteDx's suggestion makes it left aligned as well
Not for all texts, but for specific length of text GeometryReader decides that Text should contains two lines:
public var body: some View {
ZStack {
if loading {
Text(text)
.foregroundColor(.clear)
.background(rectReader($frame))
.fixedSize(horizontal: false, vertical: true) //Setting vertical to false - solve unwanted behaviour, but I can have multiline text and it makes multiline text single line, so I can't solve it by this way
VStack {
RoundedRectangle(cornerRadius: 8)
.frame(width: frame.width, height: 16)
.foregroundColor(.colorDivider)
if frame.height > 24 {
RoundedRectangle(cornerRadius: 8)
.frame(width: frame.width, height: 16)
.foregroundColor(.colorDivider)
}
}
} else {
Text(text)
.accessibility(identifier: accessibilityIdentifier)
.fixedSize(horizontal: false, vertical: true)
}
}
.background(Color.red)
}
func rectReader(_ binding: Binding<CGRect>) -> some View {
return GeometryReader { geometry -> AnyView in
let rect = geometry.frame(in: .global)
DispatchQueue.main.async {
binding.wrappedValue = rect
}
return AnyView(Rectangle().fill(Color.clear))
}
}
As a result:
But should be:
As you can see in the first image wrong second line, but in the second image - wrong third line (multiline text)
The reason is not in Text but in shapes. The fixed variant is to use maxWidth instead of strong width. Tested with Xcode 11.4 / iOS 13.4
RoundedRectangle(cornerRadius: 8).stroke(Color.gray)
.frame(maxWidth: frame.width).frame(height: 16)
How do I position views relative to their top left corner in swiftUI? The "position" modifier moves the views relative to their center coordinates. So .position(x: 0, y: 0) places a views center coordinate in the top left of the screen.
I want to place a views top left coordinate in the top left of the screen, How do I do this?
struct HomeView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)
Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
}
.position(x: 0, y: 0)
}
}
Currently, the left half of my view is cut off the screen, how do I prevent this? Also, how do I align relative to the safe area?
I miss UIKit 😪
#Asperi 's answer will solve the problem. But, I think we should use Spacer() rather than Color.clear and ZStack.
Spacer is specifically designed for these scenarios and makes the code easier to understand.
struct HomeView: View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)
Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
Spacer()
}
Spacer()
}
}
}
SwiftUI layout system is different from UIKit.
It asks each child view to calculate its own size based on the bounds of its parent view. Next, asks each parent to position its children within its own bounds.
https://www.hackingwithswift.com/books/ios-swiftui/how-layout-works-in-swiftui
If I correctly understand your goal the .position is not appropriate instrument for it. SwiftUI layout works better without hardcoding.
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
struct HomeView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Color.clear
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)
Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
You can use:
offset(x: CGFloat = 0, y: CGFloat = 0) -> some View
So you will be relative to current object position.
https://developer.apple.com/documentation/swiftui/gaugestyleconfiguration/currentvaluelabel-swift.struct/offset(x:y:)