I am making an earthquake application on SwiftUI. The data I receive includes the magnitude of the earthquake and I want the text I print on the screen to change color according to this magnitude.
For example, I can say: I want it to show green if it is less than 3, yellow if it is between 3 and 5, red if it is between 5 and 7, black if it is greater than 7.
Can you help me?
Thanks
There are many ways to solve this problem, I believe if you know how to do an if-else statement, you can do it yourself. Since you're struggling with probably swift or swiftUI itself
here are 3 ways ways you can do it
first is
struct ContentView: View {
#State var quake = 2
var body: some View {
if (quake < 3) {
Text("Earthquake")
.foregroundColor(.green)
} else if (quake <= 5) {
Text("Earthquake")
.foregroundColor(.yellow)
} else if // etc...
}
}
but it's very inefficient, therefore the 2nd solution is to iterate it within the foreground color itself
struct ContentView: View {
#State var quake = 2
var body: some View {
Text("Earthquake")
.foregroundColor(quake < 3 ? .green : quake <= 5 ? .yellow : // etc)
}
}
all you have to do is put the
condiditon ? if its true : if codition ? if it's true : if condition //etc untill else
it's basicaly like
if condition then do something
else if condition
up untill
else
the last solution is to create a function that returns the key, i believe this one is far more beginner-friendly compared to the 2nd one
struct ContentView: View {
#State var quake = 2
private func getColor() -> Color {
if quake <= 3 {
return .green
} else if quake <= 5 {
return .yellow
}
return .black // default, random, you decide the last condition
}
var body: some View {
Text("Earthquake")
.foregroundColor(self.getColor())
}
}
You can use ternary operators to show green if it is less than 3, yellow if it is between 3 and 5, red if it is between 5 and 7, black if it is greater than 7. For example:
struct ContentView: View {
#State var quake = 2
var body: some View {
Text("quake in πΊπ¦")
.foregroundColor(quake < 3 ? .green
: quake <= 5 ? .yellow
: quake <= 7 ? .red
: .black)
}
}
Related
I am currently working on an app with class schedule. I need to implement a swipable week view just like in Apple's "Calendar" app. There is no built-in solution for this in SwiftUI (Maybe I didn't find it), so I used SwiftUIPager library. It kinda works, but I have to provide it an array of elements. Then Pager use this elements to create pages. This does not suit me, so I decided to dynamically add elements of the array when I approach its border.
struct WeekObserverView: View {
let vm: WeekObserverViewModel = WeekObserverViewModel()
let OnDayChanged: (Date)->Any
#State private var selectedDay = Date()
#State private var currentPage = Page.withIndex(2)
#State private var data = Array(-2..<3)
#State private var disableButtons = false
var body: some View {
VStack(spacing: 0){
VStack(alignment: .center, spacing: 0){
Pager(page: currentPage,
data: data,
id: \.self) {
self.generateWeekView($0)
}
.singlePagination(ratio: 0.5, sensitivity: .high)
.onPageWillChange({ (page) in
withAnimation {
//Moving forward or backward a week
selectedDay = selectedDay + TimeInterval(604800) * Double((page - currentPage.index))
}
_ = OnDayChanged(selectedDay)
})
.onPageChanged({
page in
//Adding new weeks when we approach the boundaries of the array
if page == 1 {
let newData = (1...5).map { data.first! - $0 }.reversed()
withAnimation {
currentPage.update(.move(increment: newData.count))
data.insert(contentsOf: newData, at: 0)
}
} else if page == self.data.count - 2 {
guard let last = self.data.last else { return }
let newData = (1...5).map { last + $0 }
withAnimation {
data.append(contentsOf: newData)
}
}
disableButtons = false
})
.onDraggingBegan({
disableButtons = true
})
.pagingPriority(.simultaneous)
.frame(height: 48)
Capsule()
.frame(width: 32, height: 6)
.foregroundColor(Color("TransparetPurple"))
.padding(4)
}
.frame(maxWidth: .infinity)
.background(Color("AccentBlue"))
//Spacer()
}
}
The problem is when I adding element to the front of array Pager loses current page and goes to the next page on the left.
I tried to add one extra page to the index, but it didn't work
currentPage.update(.move(increment: newData.count + 1))
Maybe there is a more simple solution without providing an array? I want Pager to just give me the offset from first loaded page.
I looked into this and checked an example called InfiniteExampleView included in the git project for SwiftUIPager.
I noticed how they are able to add items to the array dynamically both in front of the current items and behind them.
I can't test your code completely since there are some missing parts to make it run the way you run it, but it seems when they insert in front of current items they update the page index as well and I don't see you do that in your code.
This is the code from the example:
if page == 1 {
let newData = (1...5).map { data1.first! - $0 }.reversed()
withAnimation {
page1.index += newData.count //<-- this line
data1.insert(contentsOf: newData, at: 0)
isPresented.toggle()
}
}
this is your code under the withAnimation part of if statement page==1:
currentPage.update(.move(increment: newData.count))
data.insert(contentsOf: newData, at: 0)
So maybe adding a line to change the index helps fix this. If not, please provide a more complete example to make it possible to debug it more easily.
I have a loading screen where I want to show a text changing its value automatically with an animation.
I have my logo rotating indefinitely without any button action
//logo
Image("reny")
.rotationEffect(.degrees(rotateDegree))
.onAppear(perform: {
withAnimation(Animation.linear(duration: 4).repeatForever(autoreverses: false)) {
self.rotateDegree = 360
}
})
I assumed it was possible to do the same for a text using a string array but it doesn't work
#State var texts = ["Find the Apartment you like", "send an application", "we'll approve you in secs baby!"]
#State var textIndex : Int = 0
//introduction text
Text(texts[textIndex]).bold()
.font(.title)
.onAppear(perform: {
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
textIndex += 1
}
})
does anybody know how to change the value of a text with an animation automatically?
my intention is to show how to use the app during this loading time.
Here is a possible approach - just to replace Text depending on index and continuously change index after appear.
Tested with Xcode 13.4 / iOS 15.5
Main part:
private func next() {
var next = textIndex + 1
if next == texts.count {
next = 0
}
withAnimation(Animation.linear(duration: 2)) {
textIndex = next
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.next()
}
}
and text itself
Text(texts[textIndex]).bold()
.font(.title).id(textIndex)
.onAppear(perform: {
next()
})
Test module on GitHub
I'm working on MacOS and I try to make NumberField β something like TextField, but for numbers. In rather big tree of views at the top I had:
...
VStack {
ForEach(instances.indices, id:\.self) {index in
TextField("",
text: Binding(
get: {"\(String(format: "%.1f", instances[index].values[valueIndex]))"},
set: {setValueForInstance(index, valueIndex, $0)})
)
}
}
...
And it worked well, but not nice:
βοΈ when I changed value, all View structure was redrawn β good
βοΈ values was updated if they were changed by another part of Views structure β good
βοΈ it was updated after each keypresses, which was annoying, when I tried to input 1.2, just after pressing 1 view was updated to 1.0. Possible to input every number but inconvenient β bad
So, I tried to build NumberField.
var format = "%.1f"
struct NumberField : View {
#Binding var number: Double {
didSet {
stringNumber = String(format: format, number)
}
}
#State var stringNumber: String
var body: some View {
TextField("" , text: $stringNumber, onCommit: {
print ("Commiting")
if let v = Double(stringNumber) {
number = v
} else {
stringNumber = String(format:format, number)
}
print ("\(stringNumber) , \(number)" )
})
}
init (number: Binding<Double>) {
self._number = number
self._stringNumber = State(wrappedValue: String(format:format, number.wrappedValue))
}
}
And It's called from the same place as before:
...
VStack {
ForEach(instances.indices, id:\.self) {index in
NumberField($instances[index].values[valueIndex])
}
}
...
But in this case it never updates NumberField View if values was changed by another part of View. Whats's wrong? Where is a trick?
I'm using the iPhone 11 as the baseline and I'm trying to get the font-size to scale up when I run it on an iPad, but I cannot get it to work.
I'm doing everything programmatically and I'm also using auto-layout on the views, textfields, labels, and textviews. I tried playing around with adjustsFontSizeToWidth and minimumScaleFactor, numberOfLines but to no avail. I can't get the font size to change when I scale up to the iPad. Here is what it looks like...
I'm still learning and I'm relatively new to programming and Swift, and I tried asking this question before, but I guess I was too vague with my question.
Please let me know if you need me to include some code with this.
I also do everything programmatically and I'd suggest you to use UIKitPlus library which will solve a lot of your problems
e.g. you could set different font-size for different devices or device types easily
TextView().font(.helveticaNeueRegular, 16 !! .iPad(24))
As you could see in the example above you could set some value as usual, and also you could provide another value for the different type of device trough !! operator.
It is really easy and convenient to use.
The whole your view controller may look like
import UIKitPlus
class MyViewController: ViewController {
#State var weight = ""
#State var repetitions = ""
#State var notes = ""
override func buildUI() {
super.buildUI()
body {
VStack {
Text("Bench Press")
.color(.black)
.font(.articoBold, 32)
.edgesToSuperview(top: 16, leading: 16)
VSpace(32)
TextField($weight)
.placeholder(AttrStr("Total weight").foreground(.lightGray).font(.articoRegular, 14))
.leftView(HStack {
HSpace(4)
Text("Weight (lbs)").color(.black).font(.articoMedium, 14)
HSpace(4)
})
.color(.black)
.font(.articoRegular, 14)
.keyboard(.numbersAndPunctuation)
.returnKeyType(.next)
.tag(0)
.shouldReturnToNextResponder()
TextField($repetitions)
.placeholder(AttrStr("Number or Reps...").foreground(.lightGray).font(.articoRegular, 14))
.leftView(HStack {
HSpace(4)
Text("Repetitions").color(.black).font(.articoMedium, 14)
HSpace(4)
})
.color(.black)
.font(.articoRegular, 14)
.keyboard(.numbersAndPunctuation)
.returnKeyType(.next)
.tag(1)
.shouldReturn { $0.resignFirstResponder() }
VSpace(32)
HStack {
Image(nil).size(40)
HSpace(8)
Text("Timer").color(.lightGray).font(.articoMedium, 14)
}
.alignment(.center)
VSpace(32)
TextView($notes)
.placeholder("Notes...")
.color(.black)
.font(.articoRegular, 14)
VSpace(32)
Text("Set 1 0 lbs - 0 Reps").color(.black).font(.articoRegular, 14)
VSpace(16)
Text("Set 2 0 lbs - 0 Reps").color(.black).font(.articoRegular, 14)
VSpace(16)
Text("Set 3 0 lbs - 0 Reps").color(.black).font(.articoRegular, 14)
VSpace(16)
Text("Set 4 0 lbs - 0 Reps").color(.black).font(.articoRegular, 14)
Space()
HStack {
Button("Next Set").onTapGesture {
print("next set tapped")
}
Button("Next Excercise").onTapGesture {
print("next excercise tapped")
}
}
VSpace(32)
}
.alignment(.center)
.edgesToSuperview()
}
}
}
I had a View which should render a GridView in the beta 4 everything worked great but in the beta 5 of Xcode 11 and beta 5 of macOS Catalina it stoped working.
struct List : View {
var rows: [[Int]]
var spacing: CGFloat = (screen.width-330)/4
var list: [ReminderModel]
var number: Int
var body: some View {
return VStack {
ForEach(rows, id: \.self) { row in
HStack(spacing: self.spacing) { //The error is at this bracket
ForEach(row) { item in
Reminder(closed: self.list[item].closed, text: self.list[item].text)
self.number % 3 == 0 ? nil : VStack() {
self.number-1 == item ? AddReminder() : nil
}
}
Spacer()
}.padding(.top, self.spacing).padding(.leading, self.spacing)
}
if self.number % 3 == 0 {
HStack() {
AddReminder().padding(.leading, self.spacing).padding(.top, self.spacing)
Spacer()
}
}
}
}
}
Error:
Unable to infer complex closure return type; add explicit type to disambiguate
Update 1:
I found that the problem is this part of the code:
self.number % 3 == 0 ? nil : VStack() {
self.number-1 == item ? AddReminder() : nil
}
I also tried this but also didn't work:
if (self.number % 3 != 0 && self.number-1 == item) {
AddReminder()
}
I simplified your code down into something I could run:
struct ContentView: View {
var rows: [[Int]] = [[0, 1, 2], [3, 4, 5]]
var body: some View {
VStack {
ForEach(rows, id: \.self) { row in
HStack {
ForEach(row) { item in
EmptyView()
}
}
}
}
}
}
...and I got this error:
Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Int' conform to 'Identifiable'
I'm guessing that in previous betas Int conformed to Identifiable and that beta 5 changed that. So to fix this, just change your second ForEach to ForEach(row, id: \.self).
Update
After removing the parts of your code that I could not run, I managed to get the same error.
Error: Unable to infer complex closure return type; add explicit type to disambiguate
It appears that ForEach is expecting one view to be returned from its body, not multiple as you have here:
ForEach(row) { item in
Reminder(closed: self.list[item].closed, text: self.list[item].text)
self.number % 3 == 0 ? nil : VStack() {
self.number-1 == item ? AddReminder() : nil
}
}
You're trying to return both a Reminder and an optional VStack, so the compiler can't determine what the return type is supposed to be. This may have worked in the past because ForEach could previously handle tuple views and no longer does - I'm not sure. Whatever the case, you need to first change the ForEach to ForEach(row, id: \.self) as I pointed out earlier, and then you have to wrap everything inside the ForEach in a group, like this:
ForEach(row, id: \.self) { item in
Group {
Reminder(closed: self.list[item].closed, text: self.list[item].text)
self.number % 3 == 0 ? nil : VStack {
self.number - 1 == item ? AddReminder() : nil
}
}
}
One last thing that I've just noticed. The name of your struct should not be List. List already exists in SwiftUI, and you shouldn't name your custom views in ways that will conflict with framework defined types. I would suggest that you rename your view to ReminderList if that adequately describes its purpose.