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)
Related
I have a chart showing a 10 day forecast using weatherkit, I want to show min and max temp per day so I use a BarMark with two values in the x axis. I want to show annotation for the min and one for the max. but somehow some of them appear double with different values.
is this an Apple issue or my code issue?
struct TenDayForecastViewChart: View {
let dayWeatherList: [DayWeather]
var body: some View {
VStack(alignment: .leading) {
Text("10-DAY FORECAST")
.font(.caption)
.opacity(0.5)
.padding()
Chart{
ForEach(dayWeatherList, id: \.date) { dailyWeather in
BarMark(xStart: .value("Temperature", dailyWeather.lowTemperature.converted(to: .fahrenheit).value),
xEnd: .value("Temperature", dailyWeather.highTemperature.converted(to: .fahrenheit).value),
y: .value("Day", dailyWeather.date.formatAsAbbreviatedDay())
)
.foregroundStyle(Color.black)
.annotation(position: .overlay, alignment: .leading) {
HStack {
//Image(systemName: "\(dailyWeather.symbolName)").foregroundColor(.white)
Text("\(dailyWeather.lowTemperature.converted(to: .fahrenheit).value, format: .number.precision(.fractionLength(0)))")
.foregroundColor(.white)
}
}
.annotation(position: .overlay, alignment: .trailing) {
Text("\(dailyWeather.highTemperature.converted(to: .fahrenheit).value, format: .number.precision(.fractionLength(0)))")
.foregroundColor(.blue)
}
}
}
//.chartLegend(position: .top, alignment: .bottomTrailing)
.chartXAxis(.hidden)
//.chartYAxis(.hidden)
.frame(height: 250)
.padding(.horizontal)
}
}
}
I suppose in your 10 days you have 2x "Sun", "Mon", "Tue" each. So the y plot value for these data points is identical, and by default BarCharts adds those together.
You can pass the full Date (not only the day String) into the BarChart and use
.value("Day", dailyWeather.date, unit: .day)
and add a custom formatting in .chartYAxis:
.chartYAxis {
AxisMarks(values: .stride(by: .day)) { _ in
AxisGridLine()
AxisTick()
AxisValueLabel(format: .dateTime.weekday(.abbreviated), centered: true)
}
}
I know you can change the font size dynamically with the .minimumScaleFactor view modifier however the issue is that I have 3 textfields in a VStack and I want them to all share the same dynamic font size.
If you run my example down below and just tap anywhere you will see that it either works as intended or sometimes 1 of them ends up smaller or larger than the other two. I can't tell if this is a bug or am I missing something here?
struct ContentView: View {
#State private var text = "Tap the screen!"
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(text)
Spacer(minLength: 20)
Text(text)
Spacer(minLength: 20)
Text(text)
Spacer(minLength: 20)
}
.lineLimit(7)
.font(.system(size: 100))
.minimumScaleFactor(0.01)
Spacer(minLength: 0)
}
.padding()
.contentShape(Rectangle())
.frame(width: 1600/2.5, height: 900/2.5)
.onTapGesture {
generateNewText()
}
}
private func generateNewText() {
var verse = ""
for _ in 0..<Int.random(in: 20...50) {
verse+="example "
}
text = verse
}
}
I want the font size to be the same for all three regardless how many or few words each line has.
But sometimes one of them is larger or smaller (larger in this case) than the other two and I need to prevent it.
I'm looking for a similar way https://github.com/stokatyan/ScrollCounter in SwiftUI
This is a very rudimentary build of the same thing. I'm not sure if you're wanting to do it on a per-digit basis, however this should give you a solid foundation to work off of. The way that I'm handling it is by using a geometry reader. You should be able to easily implement this view by utilizing an HStack for extra digits/decimals. The next thing I would do would be to create an extension that handles returning the views based on the string representation of your numeric value. Then that string is passed as an array and views created for each index in the array, returning a digit flipping view. You'd then have properties that are having their state observed, and change as needed. You can also attach an .opacity(...) modifier to give it that faded in/out look, then multiply the opacity * n where n is the animation duration.
In this example you can simply tie your Digit value to the previewedNumber and it should take over from there.
struct TestView: View {
#State var previewedNumber = 0;
var body: some View {
ZStack(alignment:.bottomTrailing) {
GeometryReader { reader in
VStack {
ForEach((0...9).reversed(), id: \.self) { i in
Text("\(i)")
.font(.system(size: 100))
.fontWeight(.bold)
.frame(width: reader.size.width, height: reader.size.height)
.foregroundColor(Color.white)
.offset(y: reader.size.height * CGFloat(previewedNumber))
.animation(.linear(duration: 0.2))
}
}.frame(width: reader.size.width, height: reader.size.height, alignment: .bottom)
}
.background(Color.black)
Button(action: {
withAnimation {
previewedNumber += 1
if (previewedNumber > 9) {
previewedNumber = 0
}
}
}, label: {
Text("Go To Next")
}).padding()
}
}
}
I need precise control over the area taken by Text. I created the most basic program that shows unexpected top and bottom spacing being added to a text. Where is this extra padding coming from? How to get rid of it?
#main
struct myApp: App {
init() { }
var body: some Scene {
WindowGroup {
Text("80")
.font(.system(size: 30, weight: .bold, design: .default))
.background(Color.red)
}
}
}
Text seems to have default padding as seen here
You can get past that by adjusting the padding to a negative amount
Replace your code with this
Text("80")
.font(.system(size: 30, weight: .bold, design: .default))
.padding(.vertical, -6)
.background(Color.red)
Here is a solution if you want to make it dynamic
struct TestView: View {
var fontSize: CGFloat = 110
var paddingSize: CGFloat {
-(fontSize * 0.23)
}
var body: some View {
Text("80")
.font(.system(size: fontSize, weight: .bold, design: .default))
.padding(.vertical, paddingSize)
.background(Color.red)
Text("Hello")
}
}
So even with a large font size of 110 like in the picture you can render it how you like
You'll need to tweak the multiplier how you see fit
Turns out that the padding top and bottom is actually not there at all. In my example with numbers this looks like wasted space, but actually certain characters do need this room as can be seen on this picture:
Since I am only going to show numbers I have used the solution by #Sergio to correct for this overspace.
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)