Here's what my code looks like, and it shows 5 BarMarks.
struct Record: Identifiable {
var id = UUID()
var altitude: Int
var speed: Double
var time: Int
}
struct TestView: View {
#State private var records:[Record] = [
Record(altitude: 15000, speed: 830, time: 1),
Record(altitude: 15000, speed: 830, time: 2),
Record(altitude: 15000, speed: 830, time: 3),
Record(altitude: 15000, speed: 830, time: 4),
Record(altitude: 15000, speed: 830, time: 5)
]
var body: some View {
VStack{
Chart(records) { record in
BarMark(x: .value("Date", String(record.time)), y: .value("Level", record.speed))
}.frame(height: 200)
}
}
}
But I want to show x-labels for only first one and last one.
So here is what I have tried. Provide empty X label strings when index is not first or last one using ternary operator.
Now it shows X-labels only for first and last one, but now all three middle BarMarks got collpased in one.
struct TestView: View {
#State private var records:[Record] = [
Record(altitude: 15000, speed: 830, time: 1),
Record(altitude: 15000, speed: 830, time: 2),
Record(altitude: 15000, speed: 830, time: 3),
Record(altitude: 15000, speed: 830, time: 4),
Record(altitude: 15000, speed: 830, time: 5)
]
var body: some View {
VStack{
Chart(0..<records.count, id: \.self) { index in
BarMark(x: .value("time", index == 0 || index == records.count-1 ? String(records[index].time) : ""), y: .value("speed", records[index].speed))
}.frame(height: 200)
}
}
}
Does anyone know why this behavior is happening and how I can prevent this from happening?
In your approach you are directly modifying the plot values. So Chart "sees" only 3 different x values: "1","","5" – and adds up all y values with the x value "".
To achieve what you want you can directly modify the xAxis appearance:
Add this modifier to your chart and leave the plot values as in your initial code.
.chartXAxis {
AxisMarks { value in
AxisGridLine()
AxisTick()
// only show first and last xAxis value label
if value.index == 0 || value.index == value.count-1 {
AxisValueLabel()
}
}
}
Related
I have been experimenting with LazyVGrid to produce a table with grid lines. I'm using XCode 13.4 & iOS15. Everything seemed to be going well until I parameterized all the column sizes, line widths, etc. The content consists of an array of row data with 5 pieces of text in each row. Here is the code:
struct StatisticsView: View {
private let columnMinWidths: [CGFloat] = [120, 50, 50, 50, 50]
private let rowHeight: CGFloat = 40
private let thinGridlineSize: CGFloat = 0.5
private let thickGridlineSize: CGFloat = 2
private let lineColour = Color.accentColor
private let columns: [GridItem]
private let rowContents = [
["Player's name", "Graham", "John", "Archie", ""],
["Round 1", "2", "3", "1", ""],
["Round 2", "3", "-", "2", ""],
["Round 3", "1", "2", "4", ""],
["Round 4", "2", "1", "1", ""]
]
init() {
self.columns = [
GridItem(.fixed(thickGridlineSize), spacing: 0),
GridItem(.flexible(minimum: columnMinWidths[0]), spacing: 0),
GridItem(.fixed(thickGridlineSize), spacing: 0),
GridItem(.flexible(minimum: columnMinWidths[1]), spacing: 0),
GridItem(.fixed(thinGridlineSize), spacing: 0),
GridItem(.flexible(minimum: columnMinWidths[2]), spacing: 0),
GridItem(.fixed(thinGridlineSize), spacing: 0),
GridItem(.flexible(minimum: columnMinWidths[3]), spacing: 0),
GridItem(.fixed(thinGridlineSize), spacing: 0),
GridItem(.flexible(minimum: columnMinWidths[4]), spacing: 0),
GridItem(.fixed(thickGridlineSize), spacing: 0),
]
}
var body: some View {
VStack(spacing: 0) {
// top horizontal gridline
lineColour.frame(height: thickGridlineSize)
LazyVGrid(columns: columns, alignment: .leading, spacing: 0) {
ForEach(0..<rowContents.count, id: \.self) { row in
lineColour.frame(height: rowHeight)
ForEach(0..<rowContents.first!.count, id: \.self) { col in
// cell contents with bottom gridline
VStack(spacing: 0) {
Text(rowContents[row][col])
.font(.caption2)
.frame(height: rowHeight - thinGridlineSize)
// tried using a colour fill but same result
// Color.green
// .frame(height: rowHeight - thinGridlineSize)
lineColour.frame(height: thinGridlineSize)
}
// vertical gridline between cells
lineColour.frame(height: rowHeight)
}
}
}
// bottom horizontal gridline
lineColour.frame(height: thickGridlineSize)
}
.padding(.horizontal, 8)
}
}
and here is what is being displayed on the simulator screen:
Can anyone see where I'm going wrong here? Any help would be much appreciated. Thanks.
You have to remove id: \.self from both loops. View Id become duplicated so it's not drawing more than one line.
I am trying to make a stopwatch with milliseconds. For this, I have copied the code from Fini Eines (https://medium.com/geekculture/build-a-stopwatch-in-just-3-steps-using-swiftui-778c327d214b)
However, I am looking for a stopwatch with milliseconds. Therefore I have added my own var's and parameters. My goal is only to have a stopwatch, which consistently shows hours, minutes, seconds, milliseconds. Features for laps are not necessary.
It just doesn't seem to work. Am I forgetting something?
This is my code:
struct Stopwatch: View {
/// Current progress time expresed in seconds
#State private var progressTime = 0
#State private var isRunning = false
/// Computed properties to get the progressTime in hh:mm:ss format
var hours: Int {
progressTime / 3600
}
var minutes: Int {
(progressTime % 3600) / 60
}
var seconds: Int {
progressTime % 60
}
var milliseconds: Int {
(progressTime % 60) / 1000
}
/// Increase progressTime each second
#State private var timer: Timer?
var body: some View {
VStack {
HStack(spacing: 10) {
StopwatchUnit(timeUnit: hours, timeUnitText: "", color: .pink)
Text(":")
.font(.system(size: 48))
.foregroundColor(.white)
.offset(y: -18)
StopwatchUnit(timeUnit: minutes, timeUnitText: "", color: .blue)
Text(":")
.font(.system(size: 48))
.foregroundColor(.white)
.offset(y: -18)
StopwatchUnit(timeUnit: seconds, timeUnitText: "", color: .green)
Text(",")
.font(.system(size: 48))
.foregroundColor(.white)
.offset(y: -18)
StopwatchUnit(timeUnit: milliseconds, timeUnitText: "", color: .green)
}
HStack {
Button(action: {
if isRunning{
timer?.invalidate()
} else {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
progressTime += 0
})
}
isRunning.toggle()
})
}
}
}
struct StopwatchUnit: View {
var timeUnit: Int
var timeUnitText: String
var color: Color
/// Time unit expressed as String.
/// - Includes "0" as prefix if this is less than 10.
var timeUnitStr: String {
let timeUnitStr = String(timeUnit)
return timeUnit < 10 ? "0" + timeUnitStr : timeUnitStr
}
var body: some View {
VStack {
ZStack {
HStack(spacing: 2) {
Text(timeUnitStr.substring(index: 0))
.font(.system(size: 48))
.frame(width: 28)
Text(timeUnitStr.substring(index: 1))
.font(.system(size: 48))
.frame(width: 28)
}
}
Text(timeUnitText)
.font(.system(size: 16))
}
.foregroundColor(.white)
}
}
extension String {
func substring(index: Int) -> String {
let arrayString = Array(self)
return String(arrayString[index])
}
}
Thanks for any help!
I'm creating a schedule app and im trying to render the activities on a calendar sort of thing. Im doing this by having a set spacing of 20 between every hour mark. This means that to position the activities I can take the hour the activity start times this set height of 20 to position it correctly. This does indeed work but when I add multiple activities the offset is wrong? But if I render every activity on its own the offset is correct.
This is the code for displaying the activities.
struct Event: Hashable {
var name: String
var start: Int
var end: Int
var y: CGFloat
var eHeight: CGFloat
var color: Color
}
struct EventIndicatorView: View {
var day = [Event]()
init() {
day.append(createEvent(name: "Matematik", start: 8, end: 9, color: Color.blue))
day.append(createEvent(name: "Svenska", start: 10, end: 11, color: Color.green))
day.append(createEvent(name: "Fysik", start: 12, end: 14, color: Color.red))
day.append(createEvent(name: "Idrott", start: 15, end: 16, color: Color.pink))
}
func createEvent(name: String, start: Int, end: Int, color: Color) -> Event {
let eHeight = CGFloat((end - start) * height * 2)
//let y = CGFloat((start*height*2) + height)
let y = CGFloat((start*2*height)+height*(end-start))
let e = Event(name: name, start: start, end: end, y: y, eHeight: eHeight, color: color)
return e
}
var body: some View {
VStack(spacing: 0) {
ForEach(day, id: \.self) { d in
HStack() {
VStack(spacing: 0) {
HStack(spacing: 0) {
Rectangle()
.fill(d.color)
.frame(width:5, height: d.eHeight)
ZStack(alignment: .topLeading) {
Rectangle()
.fill(d.color.opacity(0.5))
.frame(height: d.eHeight)
Text(d.name)
.padding(EdgeInsets(top: 4, leading: 4, bottom: 0, trailing: 0))
.foregroundColor(d.color)
.font(.subheadline.bold())
}
}
.cornerRadius(4)
.offset(x: 60, y: d.y)
}
}
.frame(height: CGFloat(height))
}
Spacer()
}
}
}
This is the code for showing the hours.
var hours = [String]()
struct CalenderView: View {
init() {
for hour in 0...24 {
if hour == 24 {
hours.append("00:00")
return
}
if hour <= 9 {
hours.append("0\(hour):00")
} else {
hours.append("\(hour):00")
}
}
}
var body: some View {
VStack(spacing: CGFloat(height)) {
ForEach(hours.indices, id:\.self) {index in
HStack(spacing: 10) {
Text(hours[index])
.frame(width: 40, height: CGFloat(height))
.font(.caption.bold())
.foregroundColor(Color(.systemGray))
Rectangle()
.fill(Color(.systemGray4))
.frame(height: 1)
}
.padding(.leading, 10)
}
}
}
}
This code results in this:
Display of multiple acctiviys
The green one should be positioned on 10 but isn't.
Here is a picture where I only display one activity:
Only one activity
But if I only display the green one its position is correct
I would like to add animating views to a parent view. I know that the parent view needs to position the children but I'm having trouble coming up with the formula to implement. I have the first couple of views right but once I get to 4 and up its a problem! I would like the views to appear in a grid with 3 columns.
Here is some reproducible code ready to be copy and pasted.
import SwiftUI
struct CustomView: View, Identifiable {
#State private var startAnimation = false
let id = UUID()
var body: some View {
Circle()
.frame(width: 50, height: 50)
.scaleEffect(x: startAnimation ? 2 : 1,
y: startAnimation ? 2 : 1)
.animation(Animation.interpolatingSpring(mass: 2, stiffness: 20, damping: 1, initialVelocity: 1))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.startAnimation = true
}
}
}
}
struct StartView: View {
#State private var userSelection: [CustomView] = []
var body: some View {
VStack(spacing: -20) {
Button("Add View") {
self.userSelection.append(CustomView())
}
LazyVGrid(columns: gridStyle) {
ForEach(Array(userSelection.enumerated()), id: \.0 ){ index, equip in
CustomView()
.position(x: widthBasedOn(index: index), y: heightBasedOn(index: index))
}
.padding([])
}
.frame(width: UIScreen.main.bounds.width * 0.5,
height: UIScreen.main.bounds.height * 0.8)
}
}
let gridStyle = [
GridItem(.flexible(minimum: 0, maximum: 100), spacing: -50),
GridItem(.flexible(minimum: 0, maximum: 100), spacing: -50),
GridItem(.flexible(minimum: 0, maximum: 100), spacing: -50)
]
private func widthBasedOn(index: Int) -> CGFloat {
if index % 3 != 0 {
if index > 3 {
let difference = index - 4
return CGFloat(index * difference * 100)
}
let answer = CGFloat(index * 100)
print("\(index) width should be: \(answer)")
return answer
}
return 0
}
private func heightBasedOn(index: Int) -> CGFloat {
if index > 3 && index < 6 {
return 100
}
return 200
}
}
struct EquipmentSelectionView_Previews: PreviewProvider {
static var previews: some View {
StartView()
}
}
Since most of your question is somewhat vague, and I am not sure about the specifics, this is my solution. Feel free to respond, and I will be glad to answer your question further with more tailored solution.
I removed many of your code that was unnecessary or overly-complicated. For example, I removed the widthBasedOn and heightBasedOn methods. I also changed the array property var userSelection: [CustomView] to var numberOfViews = 0.
Note: Both your original code and my solution cause all the circles to wiggle up and down, whenever a new circle is added.
I suggest that you copy paste this code snippet, run it in Xcode, and see if this is what you want.
struct CustomView: View, Identifiable {
#State private var startAnimation = false
let id = UUID()
var body: some View {
Circle()
//Changing the frame size of the circle, making it bigger or smaller
.frame(width: startAnimation ? 100 : 50, height: startAnimation ? 100 : 50)
.animation(Animation.interpolatingSpring(mass: 2, stiffness: 20, damping: 1, initialVelocity: 1))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.startAnimation = true
}
}
}
}
struct StartView: View {
//View will display this number of circles
#State private var numberOfViews = 0
var body: some View {
VStack() {
Button("Add View") {
self.numberOfViews += 1
}
.padding(.top, 100)
Spacer()
LazyVGrid(columns: gridStyle) {
//Add a new circle CustomView() to the LazyVGrid for each number of views
ForEach(0..<numberOfViews, id: \.self ){view in
CustomView()
}
}
}
}
//3 columns, flexible spacing for elments. In this case, equal amount of spacing.
let gridStyle = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
]
}
struct EquipmentSelectionView_Previews: PreviewProvider {
static var previews: some View {
StartView()
}
}
Limiting number of circles
To limit the number of circles:
if numberOfViews < 9 {
self.numberOfViews += 1
}
Positioning the button
To position the button, you can add padding:
Button("Add View") {
if numberOfViews < 9 {
self.numberOfViews += 1
}
}
.padding(.top, 100)
Overlap vs. No Overlap
Using there .frame modifier will not have any overlap:
.frame(width: startAnimation ? 100 : 50, height: startAnimation ? 100 : 50)
But if you do want overlap, use .scaleEffect:
.scaleEffect(x: startAnimation ? 2 : 1,
y: startAnimation ? 2 : 1)
P.S. Unfortunately, I can't show you the results with GIF images because Stackoverflow keep giving me upload errors.
I am fairly new to SwiftUI and I am attempting to follow a WWDC-2019 app presentation called "Introducing SwiftUI: Building Your First App". so far, everything is working fine except that images are not rendering on the view as expected. My assets xcasettes are properly loaded with nine thumbnails images and each image is affixed with a room name. Below is my the code for the contentView:
import SwiftUI
struct ContentView: View {
var rooms: [Room] = []
var body: some View {
List(rooms) { room in
Image(room.thumbnailName)
VStack (alignment: .leading){
Text(room.name)
Text("\(room.capacity) people")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(rooms: testData)
}
}
The code that follows below is an identifiable struct called "Room" and data called "testData"
import SwiftUI
struct Room : Identifiable {
var id = UUID()
var name: String
var capacity: Int
var hasVideo: Bool = false
var imageName: String { return name}
var thumbnailName: String { return name + "Thumb"}
}
let testData = [
Room(name: "Observation Deck", capacity: 6, hasVideo: true),
Room(name: "Executive Suite", capacity: 8, hasVideo: false),
Room(name: "Charter Jet", capacity: 16, hasVideo: true),
Room(name: "Dungeon", capacity: 10, hasVideo: true),
Room(name: "Panorama", capacity: 12, hasVideo: false),
Room(name: "Oceanfront", capacity: 8, hasVideo:false),
Room(name: "Rainbow Room", capacity: 10, hasVideo: true),
Room(name: "Pastoral", capacity: 7, hasVideo: false),
Room(name: "Elephant Room", capacity: 1, hasVideo: false)
]
I tried changing the name of the thumbnails to "thumbnailName", "Thumb", "room" but neither worked. I haven't been able to associate the word "Thumb" to anything in the code and I suspect it may be the root of the problem.
As requested, below is a screenshot of the contents of the assets:
Contents of my assets
Change the line:
var thumbnailName: String { return name + "Thumb"}
To this:
var thumbnailName: String { return name}
The images don‘t change automatically their size just by adding „thumb“ somewhere. Either you add small images with the „thumb“-name in the assets or you just change the size of your images by adding resizable and frame.
The images don‘t change automatically their size just by adding "thumb" in the file ROOM where have your struct code then quit the word "thumb" after go and put the next line of code down of Image (room.thumbnailName)
ROMS file:
var thumbnailName: String {return name}
contentView:
Image (room.thumbnailName)
.resizable()
.frame(width: 32.0, height: 32.0)