How to have text fit within boundaries of circular shape/image SwiftUI - swift

I can't seem to grasp how to make sure that my text overlays and fits within the circular image's boundaries. This is what I have so far.
Circle()
.frame(width: 100, height: 100)
.overlay(
Text("Reaaaallllllyyyyyy Looooooongggggg")
.foregroundColor(Color.red)
.frame(minWidth: 0, maxWidth: .infinity)
)
Is there a way in SwiftUI to get this to work? How would I go about achieving this?

struct ClippedCircleView: View {
#State var text: String = "Reaaaallllllyyyyyy Looooooongggggg"
// Make this any number you are comfortable with
#State var letterLimit: Int = 12
var body: some View {
Circle()
.frame(width: 100, height: 100)
.overlay(
Text(text)
//Limit to 1 line until your letterLimit
.lineLimit(text.count <= letterLimit ? 1 : nil)
//Clips it to shape
.clipShape(ContainerRelativeShape()).padding()
.foregroundColor(Color.red)
//This makes super tiny letters so adjust to your use case
.minimumScaleFactor(0.1)
)
}
}

Related

How can make transition stay in own zIndex until ending the transition in SwiftUI?

I have code like this in below:
struct ContentView: View {
#State private var show: Bool = false
var body: some View {
ZStack {
Button("show") { show.toggle() }.foregroundColor(.black)
//.zIndex(1)
if (show) {
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.transition(AnyTransition.asymmetric(insertion: .offset(x: 0, y: 300), removal: .offset(x: 0, y: 300)))
.onTapGesture { show.toggle() }
//.zIndex(2)
}
}
.frame(width: 300, height: 300)
.padding()
.animation(.linear(duration: 1.5), value: show)
}
}
The issue with this code is that in insertion view stays in correct zIndex(in Top layer) but in removal goes to wrong zIndex(in Bottom layer), I can correct this issue with using direct zIndex but the goal of this question is to find a way without using zIndex modifier, I thing it is possible but not sure how, maybe it has something to do with transition.
This behavior seems like a bug.
I think this is a side effect of the fact that as soon as you remove the Circle() View, it is immediately gone, and the animation happens after the fact. So upon removal, there is instantly just one item in the ZStack and it is on top.
A workaround is to not completely remove a view from the ZStack. This can be accomplished by wrapping the if show { } with an HStack, VStack, or ZStack:
struct ContentView: View {
#State private var show: Bool = false
var body: some View {
ZStack {
Button("show") { show.toggle() }.foregroundColor(.black)
//.zIndex(1)
HStack { // Note: VStack and ZStack also work
if (show) {
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.transition(AnyTransition.asymmetric(insertion: .offset(x: 0, y: 300), removal: .offset(x: 0, y: 300)))
.onTapGesture { show.toggle() }
//.zIndex(2)
}
}
}
.frame(width: 300, height: 300)
.padding()
.animation(.linear(duration: 1.5), value: show)
}
}
In this case, while the Circle() View is immediately removed, the HStack to which it belongs remains, and it remains in the same position in the ZStack thus fixing the animation.
That said, I'm not sure why adding explicit .zIndex() modifiers helps.
Note: Wrapping if show() { } in a Group does not help because Group is not a View.

SwiftUI - Animate height of rectangle from 0 to height

I am attempting to animate the height of a RoundedRectangle() from 0 to its new height, I would like it to grow from its current position upwards when my device shakes. As it currently stands, I have a onShake function which I want to activate the growing of the rectangle.
struct View1: View {
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
Text("Hola")
.foregroundColor(.black)
.font(.headline)
RoundedRectangle(cornerRadius: 5)
.frame(width: 100, height: 0, alignment: .center)
.foregroundColor(Color("RedColour"))
.onShake {
withAnimation {
self.frame(width: 100, height: 100)
}
}
}
}
}
As you can probably tell I am very new to SwiftUI and have very limited knowledge of how animations work. Feel free to grill me for it, but I would also love some help with attempting to grow this rectangle upwards when my device shakes.
Much love!
You could try this. But as the information you provided do not include how you want to use this, this might be incomplete.
struct View1: View {
#State private var height: CGFloat = 0 // add this to modify
// the height of the Rounded Rectangle
let targetHeight: CGFloat = 100 // targetHeight var to syncronise with the VStack
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
Text("Hola")
.foregroundColor(.black)
.font(.headline)
.background{
VStack{
Spacer(minLength: 0)
RoundedRectangle(cornerRadius: 5)
.frame(width: 100, height: height)
.foregroundColor(.red)
.onShake {
withAnimation(.linear(duration: 5)) {
height = targetHeight
}
}
}.frame(height: targetHeight) // VStack with fixed height
//to animate from bottom up
}
}
}
}

How can I make moving/live dash line in SwiftUI?

I have this Rectangle which I want give moving dash effect to it! If the Shape was Circle I could simply rotate the view with animation, since this is a Rectangle! I have no other idea to make that goal possible!
struct ContentView: View {
var body: some View {
Rectangle()
.strokeBorder(style: StrokeStyle(lineWidth: 5, dash: [10]))
.frame(width: 200, height: 200)
.foregroundColor(Color.blue)
}
}
This is sometimes called the “marching ants” effect.
Animate the dashPhase property of the StrokeStyle. You want the phase to go from 0 to the total length of the dash pattern. Since you only provided one value in the dash pattern, that value is used for the on-part and the off-part. So the total length of your pattern is 10 + 10 = 20.
struct ContentView: View {
#State var phase: CGFloat = 0
var body: some View {
Rectangle()
.strokeBorder(
style: StrokeStyle(
lineWidth: 5,
dash: [10],
dashPhase: phase))
.animation(
Animation.linear(duration: 1)
.repeatForever(autoreverses: false),
value: phase)
.frame(width: 200, height: 200)
.foregroundColor(Color.blue)
.onAppear {
phase = 20
}
}
}

SwiftUI - LazyVGrid spacing

I have a LazyVGrid like so in SearchView.swift file
let layout = [
GridItem(.flexible()),
GridItem(.flexible())
]
NavigationView {
ZStack {
VStack {
....Some other stuff here
ScrollView (showsIndicators: false) {
LazyVGrid(columns: layout) {
ForEach(searchViewModel.allUsers, id: \.uid) { user in
NavigationLink(destination: ProfileDetailedView(userData: user)) {
profileCard(profileURL: user.profileURL, username: user.username, age: user.age, country: user.city)
}
}
}
}
}
}
}
My profileCard.swift looks like:
var body: some View {
ZStack {
Image.image(urlString: profileURL,
content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
}
)
.frame(width: 185, height: 250)
.overlay(
LinearGradient(gradient: Gradient(colors: [.clear, .black]), startPoint: .center, endPoint: .bottom)
)
.overlay(
VStack (alignment: .leading) {
Text("\(username.trimmingCharacters(in: .whitespacesAndNewlines)), ")
.font(.custom("Roboto-Bold", size:14))
.foregroundColor(Color.white)
+ Text("\(age)")
.font(.custom("Roboto-Light", size:14))
.foregroundColor(Color.white)
HStack {
Text("\(country)")
.font(.custom("Roboto-Light", size:14))
.foregroundColor(.white)
}
}
.padding(.leading, 15)
.padding(.bottom, 15)
,alignment: .bottomLeading
)
.cornerRadius(12)
}
}
This is producing 2 different spaces on different screen sizes:
iPhone 12:
iPhone 12 Pro Max
Im trying to get the same amount of spacing between the cards (horizontal and verticle) and the around the cards on all devices, any help on achieving this?
UPDATE
Following the example by #Adrien has gotten me closer to the problem, but when I use an image, the results change completely
let columns = Array(repeating: GridItem(.flexible(), spacing: 20, alignment: .center), count: 2)
ScrollView (showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(searchViewModel.allUsers, id: \.uid) { user in
NavigationLink(destination: ProfileDetailedView(userData: user)) {
HStack {
Image("placeholder-avatar")
.resizable()
.aspectRatio(contentMode: .fill)
}
.frame(maxWidth: .infinity, minHeight: 240) // HERE 1
.background(Color.black)
.cornerRadius(25)
}
}
}.padding(20)
}
The array of GridItem only fixes the behavior of the container of each cell. Here it is flexible, but that does not mean that it will modify its content.
The View it contains can have three different behaviors :
it adapts to its container:
like Rectangle, Image().resizable(), or any View with a .frame(maxWidth: .infinity, maxHeight: .infinity)) modifier.
it adapts to its content (like Text, or HStack).
it has a fixed size (as is your case with .frame(width: 185, height: 250))
If you want the same spacing (vertical and horizontal) between cells, whatever the device, the content of your ProfileDetailedView must adapt to its container :
you have to modify your cell so that it adopts behavior 1.
you can use the spacing parameters of yours GridItem (horizontal spacing) and LazyVGrid (vertical).
Example:
struct SwiftUIView5: View {
let columns = Array(repeating: GridItem(.flexible(), spacing: 20, alignment: .center), count: 2) // HERE 2
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) { // HERE 2
ForEach((1...10), id: \.self) { number in
HStack {
Text(number.description)
.foregroundColor(.white)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, minHeight: 200) // HERE 1
.background(Color.black)
.cornerRadius(25)
}
}
.padding(20) // HERE 2
}
}
}
Actually, the spacing is a function of the screen size, in your case. You have hard-coded the size of the card (185x250), so the spacing is as expected, a bit tighter on smaller screens, bit more airy on larger screens.
Note that the vertical spacing is consistent and as expected. This seems logical because there is no vertical size constraint.
BTW, the two screenshots are not the same size, otherwise this aspect would be more obvious.
If you want to keep the size of the card fixed, you can perhaps at least keep the horizontal space between them constant, but then you will have larger margins on the sides.
Or you can adjust the size of the card dynamically. Check out GeometryReader, which will help you access runtime screen sizes.

How to access parents frame in SwiftUI

Still working on SwiftUI, but I got a problem.
I created an Image with a dynamic frame, and I want a label which needs to be half width of the image (e.g Image width = 300; Label width 150)
In UIKit I should code something like this:
Label.widthAnchor.constraint(image.widthAnchor, multiplier: 1/2).isActive = true
I tried something similar in SwiftUI:
Image(systemName: "j.circle.fill")
.resizable()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
Text("Placeholder").frame(width: Image.frame.width/2, height: 30)
But this doesn't work.
What shall I do?
Why not setting hard sizes for both then :
Image(systemName: "j.circle.fill")
.resizable()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
Text("Placeholder")
.frame(width: UIScreen.main.bounds.width/2, height: 30)
If you want to change the whole size of the component in one place you could just do it like this :
struct MyView : View {
private var compWidth: CGFloat { UIScreen.main.bounds.width }
var body: some View {
VStack {
Image(systemName: "j.circle.fill")
.resizable()
.frame(width: compWidth, height: compWidth)
Text("Placeholder")
.frame(width: compWidth/2, height: 30)
}
}
}
Or, for a more elegant approach you could look into GeometryReader.