Since the update to iOS14, the textbox is no longer expanded so that the arrows no longer adapt to the different lengths of text.
As shown in the gif, the text width remains and too long content is cut short with an ellipsis. Can you help me how to update my code?
#State var teamName: [String] = ["blue", "red", "green", "yellow"]
[…]
HStack(spacing: 5) {
Image(systemName: "arrowtriangle.left.fill")
.font(Font.system(size: 12 ,weight: .regular))
Text(teamName[selectedLeftTeam])
.font(Font.system(size: 24 ,weight: .bold, design: .monospaced).smallCaps())
Image(systemName: "arrowtriangle.right.fill")
.font(Font.system(size: 12 ,weight: .regular))
}
.foregroundColor(Color("primaryColor")).opacity(0.2)
.gesture(
DragGesture()
.onChanged({
action in
self.swipeOffsetX = Double(action.translation.width * 0.75)
})
.onEnded({
action in
let left = self.swipeOffsetX < 30
let right = self.swipeOffsetX > 30
if left { self.selectedLeftTeam += 1 }
if right { self.selectedLeftTeam -= 1 }
self.swipeOffsetX = 0 // reset
})
)
You can use fixedSize to make your Text use as much space as it's necessary:
Text(teamName[selectedLeftTeam])
.font(Font.system(size: 24 ,weight: .bold, design: .monospaced).smallCaps())
.fixedSize()
You can also limit it to one dimension only - if you want to do it horizontally:
.fixedSize(horizontal: true, vertical: false)
Related
I hope you're all well!
I have a (potentially) silly question. I'm attempting to learn SwiftUI development by recreating the Instagram UI.
Right now, I'm working on the user page that shows a single user and their profile statistics. I'm seeing some weird behaviour with my string formatting.
HStack (spacing: 24){
VStack (alignment: .center, spacing: 2) {
Text(postCountString)
.font(.system(size: 16, weight: .bold))
Text("Posts")
.font(.system(size: 13, weight: .regular))
}
VStack (alignment: .center, spacing: 2) {
Text(followerCountString)
.font(.system(size: 16, weight: .bold))
Text("Followers")
.font(.system(size: 13, weight: .regular))
}
VStack (alignment: .center, spacing: 2) {
Text(followsCountString)
.font(.system(size: 16, weight: .bold))
Text("Follows")
.font(.system(size: 13, weight: .regular))
}
}
.frame(maxWidth: .infinity)
.padding(.leading, 16)
.onAppear {
postCountString = UIHelperFunctions.FormatCountNumber(number: creator.posts.count)
followerCountString = UIHelperFunctions.FormatCountNumber(number: creator.followerCount)
followsCountString = UIHelperFunctions.FormatCountNumber(number: creator.followsCount)
}
Above is the View component that shows the main statistics of a creator (Posts, Followers, Follows). When the view is displayed, it should run each statistic through my UIHelperFunction that formats the number into a nice readable string see function
import Foundation
class UIHelperFunctions {
static func FormatCountNumber(number: Int) -> String {
guard number > 1000 else {
return String(number)
}
guard number > 10000 else {
return String(format: "%.1fK", Double(number/1000))
}
guard number > 1000000 else {
return String(format: "%dK", number/1000)
}
return String(format: "%.1fM", Double(number/1000000))
}
}
// Example with input: 32495
print(UIHelperFunctions.FormatCountNumber(number: 32495)) // Output: 32K
However, when I input a value between 1000 and 10000, it returns a close value; however, it leaves the decimal place as 0. Below is an image that shows what I see on the screen when I input the value 6748. I would expect it to output 6.7K, but I'm seeing 6.0K.
Am I doing something silly? If not, am I misunderstanding how floating point maths works in SwiftUI?
Any help would be greatly appreciated; let me know if you need more context or code.
Thanks in advance :)
Int divided on Int will be Int, converting it to Double is too late, you need instead to divide double on double, like
guard number > 10000 else {
return String(format: "%.1fK", Double(number)/Double(1000)))
}
SwiftUI does formatting for us and it automatically updates the UILabel on screen when the region settings change, simply do:
Text(creator.posts.count, format: .number)
or
Text("Count: \(creator.posts.count, format: .number)")
To get the M, K etc. in the future this should work (if this ever gets approved)
extension NumberFormatter {
static var compact: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .compact
return f
}()
}
Text(creator.posts.count, formatter: NumberFormatter.compact)
I want to limit the width of a Text to a given number. I thought that using frame(maxWidth:) would do the job, however it does not behave as expected at all. It seems that setting a max width makes the text grow to this size, whatever the size it should be.
Here's a code snippet:
VStack(spacing: 20) {
VStack {
Text("Some Text")
.frame(maxWidth: 200)
}
.background(Color.yellow)
.border(.black, width: 1)
VStack {
Text("A longer text that should wrap to the next line.")
.frame(maxWidth: 200)
}
.background(Color.yellow)
.border(.black, width: 1)
}
Expected result:
Actual result:
Generally, Text automatically grow/shrink by itself. To solve your problem, just set maxWidth to your VStack instead of Text to control your maxWidth, then add backgrounds modifiers to your text instead.
Code is below the image:
VStack(spacing: 20) {
VStack {
Text("Some Text")
.background(Color.yellow)
.border(.black, width: 1)
}
.frame(maxWidth: 200)
VStack {
Text("A longer text that should wrap to the next line.")
.background(Color.yellow)
.border(.black, width: 1)
}
.frame(maxWidth: 200)
}
The VStack adjusts its width to the max width of its elements. This is the native behavior of the VStack.
However, if you do the following you'll get the result you want:
VStack(spacing: 20) {
VStack {
Text("Some Text")
.background(Color.yellow)
.border(.black, width: 1)
}.frame(maxWidth: 200)
VStack {
Text("A longer text that should wrap to the next line.")
.frame(maxWidth: 200)
}.background(Color.yellow)
.border(.black, width: 1)
}
Assuming that we need a generic component which should work similarly with specified behavior independently of provided string, like
VStack(spacing: 20) {
TextBox("Some Text")
.background(Color.yellow)
.border(.black, width: 1)
TextBox("A longer text that should wrap to the next line.")
.background(Color.yellow)
.border(.black, width: 1)
}
then some run-time calculations required.
Here is a demo of possible solution (with real edge markers). Tested with Xcode 13.4 / iOS 15.5
Main part:
struct TextBox: View {
private let string: LocalizedStringKey
private let maxWidth: CGFloat
#State private var width: CGFloat
init(_ text: LocalizedStringKey, maxWidth: CGFloat = 200) { // << any default value
// ...
}
// ...
Text(string)
.background(GeometryReader {
Color.clear
.preference(key: ViewSideLengthKey.self, value: $0.frame(in: .local).size.width)
})
.onPreferenceChange(ViewSideLengthKey.self) {
self.width = min($0, maxWidth)
}
.frame(maxWidth: width)
Test code on GitHub
I have some code that was working in iOS 14, but in 15, isn’t quite. The cells pictured below can have one or two lines of title. In iOS 14 (on the right), the Spacer() element between the title and the listing count text expands as expected so that the cells are all the same height. In iOS 15, it does not, and the cell shrinks, and gets centered in its row (see the “A-Train” item).
The code for the cell looks like:
ZStack
{
Color.white
.cornerRadius(8)
VStack(alignment: .leading, spacing: 0)
{
// Listing Image…
GeometryReader
{ geom in
KFImage(self.imageURL)
.cancelOnDisappear(true)
.placeholder {
Image("product-placeholder")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geom.size.width, height: geom.size.width)
}
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geom.size.width, height: geom.size.width)
.cornerRadius(4)
.clipped()
}
.aspectRatio(contentMode: .fit)
.padding(.bottom, 12)
// Title…
Text("\(self.title)")
.font(.custom("Poppins", size: 14.0).weight(.semibold))
.multilineTextAlignment(.leading)
.lineLimit(2)
Spacer(minLength: 0) // Not expanding in iOS 15
// Listing Count…
if let listingCount = self.listingCount
{
Text("\(listingCount) listings")
.font(.custom("Inter", size: 12.0).weight(.medium))
.foregroundColor(.secondaryText)
.padding(.vertical, 8)
}
// Price…
if let price = self.price
{
Text("\(Self.priceFormatter.string(from: price as NSNumber)!)")
.font(.custom("Inter", size: 14.0).weight(.semibold))
}
}
.padding(12)
}
The columns for the LazyVGrid is Array(repeating: .init(.flexible(), spacing: 20.0), count: 2).
I can’t tell if iOS 14 or 15 has the bug.
I am having exactly the same issue here. A possibility is to work around it, at least if you are using a LazyVGrid or VGrid. This can be done by aligning the grid items to the top.
Array(repeating: .init(.flexible(), spacing: 20.0), count: 2, alignment: .top)
I am aware this will not fully fix the issue, but it will align them at the top, not centered.
i trying to position plus sign in the middle of circle, but it doesnt work properly and goes a little bit lower
.navigationBarItems(trailing: Button(action: {print("add")}, label: {
Circle()
.accentColor(.green)
.frame(width: 30, height: 30, alignment: .center)
.overlay(Text("+").accentColor(.white), alignment: .center)
}))
use Image(systemName: "plus").foregroundColor(.white) instead of Text("+")
In text "+" symbol doesn't have to be in the middle of the view because of text layout.
SFSymbols are more convenient in this regard. Also you can specify size with .font(.system(size: 10))
Try this button. Here size is the same as yours, but the tappable area is larger.
struct YourReusableButton: View {
var action: (() -> Void)? = nil
var body: some View {
Button(action: buttonAction) {
ZStack {
Circle()
.frame(width: 30, height: 30)
.foregroundColor(.green)
Image(systemName: "plus")
.foregroundColor(.white)
.imageScale(.small)
.frame(width: 44, height: 44)
}
}.padding()
}
func buttonAction() { action?() }
}
you can use offset to adjust the text position, like this:
Text("+").offset(x: -1, y: -2)
You can use Text's baselineOffset modifier to adjust the text vertical offset. But that's not a good idea.
You should use an image object such as SFSymbol image or PNG image.
If I put several Views in a row with no spaces (or very little space between them), and attach some Gesture action, I can't correctly detect, which one is tapped. It looks like there is some padding inside gesture that I can't remove.
here's the example code:
struct ContentView: View {
#State var tap = 0
#State var lastClick = CGPoint.zero
var body: some View {
VStack{
Text("last tap: \(tap)")
Text("coordinates: (x: \(Int(lastClick.x)), y: \(Int(lastClick.y)))")
HStack(spacing: 0){
ForEach(0...4, id: \.self){ind in
RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color.gray)
.overlay(Text("\(ind)"))
.overlay(Circle()
.frame(width: 4, height: 4)
.foregroundColor(self.tap == ind ? Color.red : Color.clear)
.position(self.lastClick)
)
.frame(width: 40, height: 50)
//.border(Color.black, width: 0.5)
.gesture(DragGesture(minimumDistance: 0)
.onEnded(){value in
self.tap = ind
self.lastClick = value.startLocation
}
)
}
}
}
}
}
and behavior:
I want Gesture to detect 0 button clicked when click location goes negative. Is there a way to do that?
I spent few hours with that problem, and just after I post this question, I found solution.
it was so simple - just to add a contentShape modifier
...
.frame(width: 40, height: 50)
.contentShape(Rectangle())
.gesture(DragGesture(minimumDistance: 0)
...