How do I calculate offset to center views in an HStack? - swiftui

I have 3 views that are each centered on consecutive iPhone screens. How do I calculate the offset to display "2" as center width in the preview?
struct debugView: View {
#State private var currentIndex = 0
#State private var offset = CGSize.zero
private let choices: [String] = [
"1",
"2",
"3",
]
var body: some View {
GeometryReader { geometry in
HStack {
AssessmentChoiceView(choice: choices[0]).position(x: (geometry.size.width / 2) + (CGFloat(0) * geometry.size.width), y: geometry.size.height / 2.3)
AssessmentChoiceView(choice: choices[1]).position(x: (geometry.size.width / 2) + (CGFloat(1) * geometry.size.width), y: geometry.size.height / 2.3)
AssessmentChoiceView(choice: choices[2]).position(x: (geometry.size.width / 2) + (CGFloat(2) * geometry.size.width), y: geometry.size.height / 2.3)
}
.offset(x: -(geometry.size.width / 2) - (CGFloat(1) * geometry.size.width))
}
}
}

Related

how to add my other images from assets to the app. It's currently showing only one image

VStack(spacing: 0) {
ForEach(foods){ food in
FoodView(food: food, size: size)
}
struct FoodView: View {
var food: Food
var size: CGSize
var body: some View {
let cardSize = size.width
//showing 3 maximum cards on display
let maxCardsDisplaySize = size.width * 3
GeometryReader{ proxy in
let _size = proxy.size
let offset = proxy.frame(in: .named("SCROLL")).minY - (size.height - cardSize)
let scale = offset <= 0 ? (offset / maxCardsDisplaySize) : 0
let reducedScale = 1 + scale
let currentCardScale = offset / cardSize
Image(food.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: _size.width, height: _size.height)
// updating anchor based on the current warning
.scaleEffect(reducedScale < 0 ? 0.001 : reducedScale, anchor: .init(x: 0.5, y: 1 - (currentCardScale / 2.4)))
//animating scale from large to actual when it's coming from bottom
.scaleEffect(offset > 0 ? 1 + currentCardScale: 1 , anchor: .top)
Text("\(offset)")
}
.frame(height: size.height)
}
}
import Foundation
struct Food: Identifiable {
var id: UUID = .init()
var imageName: String
var title: String
var price: String
}
var foods: [Food] = [
.init(imageName: "Beef", title: "Tasty Mexican Tacos", price: "$30.5"),
.init(imageName: "Tacos", title: "Roasted beef", price: "$23.5"),
.init(imageName: "Chocolate", title: "IceCream Chocolate", price: "$15.8"),
.init(imageName: "Pancakes", title: "American Pancakes", price: "$5.00"),
.init(imageName: "Salad", title: "French beans Salad", price: "$4.00"),
.init(imageName: "Fruits", title: "Fresh Fruits", price: "$7.00")
]
var body: some View {
GeometryReader {
let size = $0.size
let cardSize = size.width
VStack(spacing: 0) {
ForEach(foods){ food in
FoodView(food: food, size: size)
}
}
.frame(width: size.width)
.padding(.top, size.height - cardSize)
.offset(y: offsetY)
.offset(y: -currentIndex * cardSize)
}
struct FoodView: View {
var food: Food
var size: CGSize
var body: some View {
let cardSize = size.width
//showing 3 maximum cards on display
let maxCardsDisplaySize = size.width * 3
GeometryReader{ proxy in
let _size = proxy.size
let offset = proxy.frame(in: .named("SCROLL")).minY - (size.height - cardSize)
let scale = offset <= 0 ? (offset / maxCardsDisplaySize) : 0
let reducedScale = 1 + scale
let currentCardScale = offset / cardSize
Image(food.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: _size.width, height: _size.height)
// updating anchor based on the current warning
.scaleEffect(reducedScale < 0 ? 0.001 : reducedScale, anchor: .init(x: 0.5, y: 1 - (currentCardScale / 2.4)))
//animating scale from large to actual when it's coming from bottom
.scaleEffect(offset > 0 ? 1 + currentCardScale: 1 , anchor: .top)
Text("\(offset)")
}
.frame(height: size.height)
}
}

How to make swiftui lazyvgrid multiple columns

I want to make a lazyvgrid with multiple columns as bellow. Large size items can be aligned with two smaller sizes.
My code is as bellow:
struct ContentView: View {
let paddingLeft: CGFloat = 22.0
var body: some View {
let widgetWidth: CGFloat = 72.0
let widgetHeight: CGFloat = widgetWidth
let col: Int = 4
let widgetGap: CGFloat = floor((UIScreen.main.bounds.size.width - paddingLeft * 2 - widgetWidth * CGFloat(col)) / CGFloat(col - 1))
let widgetWidthx2: CGFloat = widgetWidth * 2 + widgetGap
let colums1 = [GridItem(.adaptive(minimum: widgetWidth), spacing: widgetGap)]
LazyVGrid(columns: colums1, spacing: widgetGap){
ForEach(0 ..< 11){ idx in
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color(hue: 0.1 * Double(idx) , saturation: 1, brightness: 1))
.frame(width: idx == 4 ? widgetWidthx2 : widgetWidth, height: widgetHeight)
}
}
.padding(.horizontal, paddingLeft)
}
}
However, the result was not as expected:
Is there any way to make swiftui lazyvgrid multiple columns?
I find a solution:
struct ContentView: View {
let paddingLeft: CGFloat = 22.0
var body: some View {
let widgetWidth: CGFloat = 72.0
let widgetHeight: CGFloat = widgetWidth
let col: Int = 4
let widgetGap: CGFloat = floor((UIScreen.main.bounds.size.width - paddingLeft * 2 - widgetWidth * CGFloat(col)) / CGFloat(col - 1))
let widgetWidthx2: CGFloat = widgetWidth * 2 + widgetGap
let colums1 = [GridItem(.adaptive(minimum: widgetWidth), spacing: widgetGap, alignment: .leading)] //-- here alignment: .leading
LazyVGrid(columns: colums1, spacing: widgetGap){
ForEach(0 ..< 11){ idx in
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color(hue: 0.1 * Double(idx) , saturation: 1, brightness: 1))
.frame(width: idx == 4 ? widgetWidthx2 : widgetWidth, height: widgetHeight)
if idx == 4 {
Color.clear
}
}
}
.padding(.horizontal, paddingLeft)
}
}

Positioning Views in ForEach SwiftUI

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.

How can i rotate an image in SwiftUI with one finger only?

I have an image in a view and i want to be able to rotate it with one finger only, how can i do that in SwiftUI?
I checked the RotationGesture but it works only with two fingers...
Thanks
Ok, i got it with this code :
https://gist.github.com/ts95/9f8e05380824c6ca999ab3bc1ff8541f
Ok fixed it with this code :
struct RotationGesture: View {
#State var totalRotation = CGSize.zero
#State var currentRotation = CGSize.zero
var body: some View {
Text("Hello, World!")
.frame(width: 150, height: 60)
.padding()
.background(Color.orange)
.cornerRadius(15)
.rotationEffect(Angle(degrees: Double(-totalRotation.width)))
.gesture(
DragGesture()
.onChanged { value in
totalRotation.width = value.translation.width + currentRotation.width
}
.onEnded { value in
currentRotation = totalRotation
}
)
}
}
But now we have to fixed the vertical movement because this solution is only working when you move around the X axis...
I want to solution to work when you make circle movement around the view you want to rotate...
one finger rotation
struct RotationGesture: View {
#State var gestureValue = CGSize.zero
var body: some View {
Text("Hello, World!")
.frame(width: 150, height: 60)
.padding()
.background(Color.orange)
.cornerRadius(15)
.rotationEffect(Angle(degrees: Double(-gestureValue.width)))
.gesture(
DragGesture().onChanged({ (value) in
self.gestureValue = value.translation
}).onEnded({ (value) in
self.gestureValue = .zero
})
)
.animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0))
}
}
Using the solutions from above this is what I have for rotating with one finger. This will keep the position when you finish rotating as well as starting the next rotation where the last one left off.
struct SwiftUIView: View {
#State private var rotation: Angle = Angle(degrees: 0)
#State private var previousRotation: Angle?
var body: some View {
Circle()
.fill(AngularGradient(gradient: Gradient(colors: [Color.red, Color.blue]), center: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, angle: .degrees(90)))
.frame(width: 760, height: 760)
.rotationEffect(rotation, anchor: .center)
.gesture(DragGesture()
.onChanged{ value in
if let previousRotation = self.previousRotation {
let deltaY = value.location.y - (760 / 2)
let deltaX = value.location.x - (760 / 2)
let fingerAngle = Angle(radians: Double(atan2(deltaY, deltaX)))
let angle = fingerAngle - previousRotation
rotation += angle
self.previousRotation = fingerAngle
} else {
let deltaY = value.location.y - (760 / 2)
let deltaX = value.location.x - (760 / 2)
let fingerAngle = Angle(radians: Double(atan2(deltaY, deltaX)))
previousRotation = fingerAngle
}
}
.onEnded{ _ in
previousRotation = nil
})
}
}
A dragGesture will give us the value of a location where user has dragged on screen (in our case, the circle). Here, the circle frame will be equal height and equal width.
Capturing the location point where the user tapped and subtracting height/2 from y point and width/2 from x point results in deltaX and deltaY. Once we have deltaX and deltaY, we can convert it into radians using the atan2 function (which is provided by the Swift Standard Library).
struct SwiftUIView: View {
#State var angle: Angle = .zero
var circleFrame: CGRect = CGRect(x: 0, y: 0, width: 300, height: 300)
var body: some View {
Circle()
.fill(AngularGradient(gradient: Gradient(colors: [Color.red, Color.blue]), center: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, angle: .degrees(90)))
.padding()
.rotationEffect(angle)
.gesture(
DragGesture()
.onChanged { value in
let deltaY = value.location.y - (circleFrame.height / 2)
let deltaX = value.location.x - (circleFrame.width / 2)
angle = Angle(radians: Double(atan2(deltaY, deltaX)))
}
)
}
}

SwiftUI - drawingGroup() changing colors

Why are my colors changing/inverting when I apply the .drawingGroup() modifier to improve performance?
It almost seems like they are inverting. Going from Red to Black
I am not getting any error messages and code works as intended if I // (don't know the term, Hashing??) out the .drawingGroup() see screenshots at the end if unclear.
struct ColorCyclingCircle: View {
var amount = 0.0
var steps = 100
var body: some View {
ZStack {
ForEach(0..<steps) { value in
Circle()
.inset(by: CGFloat(value))
.strokeBorder(LinearGradient(gradient: Gradient(colors: [
self.color(for: value, brightness: 1),
self.color(for: value, brightness: 0.5)
]), startPoint: .top, endPoint: .bottom), lineWidth: 2)
}
}
.drawingGroup()
}
func color(for value: Int, brightness: Double) -> Color {
var targetHue = Double(value) / Double(self.steps) + self.amount
if targetHue > 1 {
targetHue -= 1
}
return Color(hue: targetHue, saturation: 1, brightness: brightness)
}
}