I have 2 errors Expected expression and Expected ')' in expression list as I am a beginner in swiftUI I cannot find the error - xcode

Hello I am following a swiftUI training on the udemy site to learn the basics today I am making a magazine application and I encounter an error Expected expression and the following Expected ')' in expression list being a beginner I don't know exactly how to solve it if you can tell me exactly why there is this error
I thank you
import SwiftUI
struct ContentView: View {
// MARK: - PROPERTY
#State private var isAnimating: Bool = false
#State private var imageScale: CGFloat = 1
#State private var imageOffset: CGSize = .zero
// MARK: - FUNCTION
func resetImageState() {
return withAnimation(.spring()) {
imageScale = 1
imageOffset = .zero
}
}
// MARK: - CONTENT
var body: some View {
NavigationView {
ZStack{
// MARK - PAGE IMAGE
Image("magazine-front-cover")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
.padding()
.shadow(color: .black.opacity(0.2), radius:12, x: 2, y: 2)
.opacity(isAnimating ? 1 : 0)
.offset(x: imageOffset.width, y: imageOffset.height) .scaleEffect(imageScale)
// MARK - 1 TAP Gesture
.onTapGesture(count: 2, perform: {
if imageScale == 1 {
withAnimation(.spring()) {
imageScale = 5
}
} else {
resetImageState()
}
})
// MARK - 2. DRAG GESTURE
.gesture(
DragGesture ()
.onChanged { value in
withAnimation(.linear(duration: 1)) {
imageOffset = value.translation
}
}
.onEnded { _ in
if imageScale <= 1 {
resetImageState()
}
}
)
} // ZSTACK
.navigationTitle("Pinch & Zoom")
.navigationBarTitleDisplayMode(.inline)
.onAppear(perform: {
withAnimation(.linear(duration: 1)) {
isAnimating = true
}
})
// MARK: - INFO PANEL
.overlay(
InfoPanel(scale: imageScale, offset: imageOffset)
.padding(.horizontal)
.padding(.top, -60)
, alignment: .top
)
// MARK: - CONTROLS
.overlay(
Group {
HStack {
}
.padding(.bottom, 30)
, alignment: .bottom
)
} //: NAVIGATION
.navigationViewStyle(.stack)
}
}
// MARK - PREVIEW
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 13")
}
}
I don't know exactly how to fix the problem

You have a leftover Group { without closing curly brackets.
This part of your code:
// MARK: - CONTROLS
.overlay(
Group {
HStack {
}
.padding(.bottom, 30)
, alignment: .bottom
)
Should be:
// MARK: - CONTROLS
.overlay(
HStack {
}
.padding(.bottom, 30)
, alignment: .bottom
)

Related

Tracking scroll position in a List SwiftUI

So in the past I have been tracking the scroll position using a scroll view but I've fallen into a situation where I need to track the position using a List. I am using a List because I want some of the built in real estate to create my views such as the default List styles.
I can get the value using PreferenceKeys, but the issue is when I scroll to far upwards, the PreferenceKey value will default back to its position 0, breaking my show shy header view logic.
This is the TrackableListView code
struct TrackableListView<Content: View>: View {
let offsetChanged: (CGPoint) -> Void
let content: Content
init(offsetChanged: #escaping (CGPoint) -> Void = { _ in }, #ViewBuilder content: () -> Content) {
self.offsetChanged = offsetChanged
self.content = content()
}
var body: some View {
List {
GeometryReader { geometry in
Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("ListView")).origin)
}
.frame(width: 0, height: 0)
content
.offset(y: -10)
}
.coordinateSpace(name: "ListView")
.onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: offsetChanged)
}
}
private struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGPoint = .zero
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
}
And this is my ContentView:
struct ContentView: View {
#State private var contentOffset = CGFloat(0)
#State private var offsetPositionValue: CGFloat = 0
#State private var isShyHeaderVisible = false
var body: some View {
NavigationView {
ZStack {
TrackableListView { offset in
withAnimation {
contentOffset = offset.y
}
} content: {
Text("\(contentOffset)")
}
.overlay(
ZStack {
HStack {
Text("Total points")
.foregroundColor(.white)
.lineLimit(1)
Spacer()
Text("20,000 pts")
.foregroundColor(.white)
.padding(.leading, 50)
}
.padding(.horizontal)
.padding(.vertical, 8)
.frame(width: UIScreen.main.bounds.width)
.background(Color.green)
.offset(y: contentOffset < 50 ? 0 : -5)
.opacity(contentOffset < 50 ? 1 : 0)
.transition(.move(edge: .top))
}
.frame(maxHeight: .infinity, alignment: .top)
)
}
.navigationTitle("Hello")
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitleDisplayMode(.inline)
.frame(maxHeight: .infinity, alignment: .top)
.background(AccountBackground())
}
}
}
The issue was that Other Views in my hierarchy (probably introduced by SwiftUI) could be sending the default value, which is why you start getting zero sometimes.
What I needed was a way to determine when to use or forward the new value, versus when to ignore it.
To do this I had to make my value optional GCPoint, with a nil default value, then in your reduce method when using PreferenceKeys you have to do:
if let nextValue = nextValue() {
value = nextValue
}
Then make sure your CGPoint is an optional value.

SwiftUI odd animation behavior with systemImage

I was messing around with a fun animation in SwiftUI when I ran into a weird problem involving animating changes to SwiftUI's SF symbols. Basically, I want to animate a set of expanding circles that lose opacity as they get farther out. This works fine when I animate the circles using the Circle() shape, but throws a weird error when I use Image(systemName: "circle"). Namely, it throws No symbol named 'circle' found in system symbol set and I get the dreaded "purple" error in Xcode. Why does my animation work with shapes but not with SF symbols?
Animation Code with Shapes:
struct ContentView: View {
let timer = Timer.publish(every: 0.25, on: .main, in: .common).autoconnect()
#State var firstIndex: Int = 0
#State var secondIndex: Int = 10
#State var thirdIndex: Int = 20
#State var fourthIndex: Int = 30
private func changeIndex(index: Int) -> Int {
if index == 40 {
return 0
} else {
return index + 1
}
}
var body: some View {
ZStack {
Circle()
.foregroundColor(.black)
.frame(width: 10, height: 10)
ExpandingCircle(index: firstIndex)
ExpandingCircle(index: secondIndex)
ExpandingCircle(index: thirdIndex)
ExpandingCircle(index: fourthIndex)
}
.onReceive(timer) { time in
withAnimation(.linear(duration: 0.25)) {
self.firstIndex = changeIndex(index: firstIndex)
self.secondIndex = changeIndex(index: secondIndex)
self.thirdIndex = changeIndex(index: thirdIndex)
self.fourthIndex = changeIndex(index: fourthIndex)
}
}
}
}
Where ExpandingCircle is defined as:
struct ExpandingCircle: View {
let index: Int
private func getSize() -> CGFloat {
return CGFloat(index * 2)
}
private func getOpacity() -> CGFloat {
if index == 0 || index == 40 {
return 0
} else {
return CGFloat(1 - (Double(index) * 0.025))
}
}
var body: some View {
Circle()
.strokeBorder(Color.red, lineWidth: 4)
.frame(width: getSize(), height: getSize())
.opacity(getOpacity())
}
}
To replicate the error, swap out ExpandingCircle in ContentView for ExpandingCircleImage:
struct ExpandingCircleImage: View {
let index: Int
private func getSize() -> CGFloat {
return CGFloat(index * 2)
}
private func getOpacity() -> CGFloat {
if index == 0 || index == 40 {
return 0
} else {
return CGFloat(1 - (Double(index) * 0.025))
}
}
var body: some View {
Image(systemName: "circle")
.foregroundColor(.red)
.font(.system(size: getSize()))
.opacity(getOpacity())
}
}
Your ExpandingCircleImage is choking because you can't have a system font of size 0, and you keep trying to feed 0 to your ExpandingCircleImage view. However, in addition to that, you don't need to use a timer to drive the animation. In fact, it makes the animation look weird because a timer is not exact. Next, your ExpandingCircle or ExpandingCircleImage should animate itself and be the complete effect.
The next issue you will encounter when you fix the font size = 0 issue, is that .font(.system(size:)) is not animatable as it is. You need to write an AnimatableModifier for it. That looks like this:
struct AnimatableSfSymbolFontModifier: AnimatableModifier {
var size: CGFloat
var animatableData: CGFloat {
get { size }
set { size = newValue }
}
func body(content: Content) -> some View {
content
.font(.system(size: size))
}
}
extension View {
func animateSfSymbol(size: CGFloat) -> some View {
self.modifier(AnimatableSfSymbolFontModifier(size: size))
}
}
The animatableData variable is the key. It teaches SwiftUI what to change to render the animation. In this case, we are animating the size of the font. The view extension is just a convenience so we can use . notation.
Another trick to animating a view like this is to have multiple animations that only go part of the way of the whole. In other words, if you use four circles, the first goes to 25%, the next from 25% to 50%, then 50% to 75%, lastly 75% to 100%. You also appear to have wanted the rings to fade as the expand, so I wrote that in as well. The code below will have two animating views, one made with a shape, and one with an SF Symbol.
struct ContentView: View {
var body: some View {
VStack {
Spacer()
ZStack {
Circle()
.foregroundColor(.black)
.frame(width: 10, height: 10)
ExpandingCircle(maxSize: 100)
}
.frame(height: 100)
Spacer()
ZStack {
Circle()
.foregroundColor(.black)
.frame(width: 10, height: 10)
ExpandingCircleImage(maxSize: 100)
}
.frame(height: 100)
Spacer()
}
}
}
struct ExpandingCircle: View {
let maxSize: CGFloat
#State private var animate = false
var body: some View {
ZStack {
Circle()
.strokeBorder(Color.red, lineWidth: 8)
.opacity(animate ? 0.75 : 1)
.scaleEffect(animate ? 0.25 : 0)
Circle()
.strokeBorder(Color.red, lineWidth: 8)
.opacity(animate ? 0.5 : 0.75)
.scaleEffect(animate ? 0.5 : 0.25)
Circle()
.strokeBorder(Color.red, lineWidth: 8)
.opacity(animate ? 0.25 : 0.5)
.scaleEffect(animate ? 0.75 : 0.5)
Circle()
.strokeBorder(Color.red, lineWidth: 8)
.opacity(animate ? 0 : 0.25)
.scaleEffect(animate ? 1 : 0.75)
}
.frame(width: maxSize, height: maxSize)
.onAppear {
withAnimation(.linear(duration: 4).repeatForever(autoreverses: false)) {
animate = true
}
}
}
}
struct ExpandingCircleImage: View {
let maxSize: CGFloat
#State private var animate = false
var body: some View {
ZStack {
Image(systemName: "circle")
.animateSfSymbol(size: animate ? (maxSize * 0.25) : 1)
.opacity(animate ? 0.75 : 1)
Image(systemName: "circle")
.animateSfSymbol(size: animate ? (maxSize * 0.5) : (maxSize * 0.25))
.opacity(animate ? 0.5 : 0.75)
Image(systemName: "circle")
.animateSfSymbol(size: animate ? (maxSize * 0.75) : (maxSize * 0.5))
.opacity(animate ? 0.25 : 0.5)
Image(systemName: "circle")
.animateSfSymbol(size: animate ? (maxSize) : (maxSize * 0.75))
.opacity(animate ? 0 : 0.25)
}
.foregroundColor(.red)
.onAppear {
withAnimation(.linear(duration: 4).repeatForever(autoreverses: false)) {
animate = true
}
}
}
}
Remember to include the AnimatableModifier in your code.

How to animate transition when adding view to hierarchy in SwiftUI

I am trying to build an overlay of "popover" views and animate transitioning in and out. Transitioning out works, but transitioning in doesn't -- as a popover view is added it just suddenly appears (wrong), but when a popover view is removed it slides out to the right (correct). How can I make the popover slide in (from right) when it's added to the view hierarchy in this code?
Fully functional code in iOS 14.
import SwiftUI
struct ContentView: View {
var body: some View {
Popovers()
}
}
struct Popovers : View {
#State var popovers : [AnyView] = []
var body : some View {
Button("Add a view ...") {
withAnimation {
popovers += [new()]
}
}
.blur(radius: 0 < popovers.count ? 8 : 0)
.overlay(ZStack {
ForEach(0..<self.popovers.count, id: \.self) { i in
popovers[i]
.frame(maxWidth: .infinity, maxHeight: .infinity)
.blur(radius: (i+1) < popovers.count ? 8 : 0)
.transition(.move(edge: .trailing)) // works only when popover is removed
}
})
}
func new() -> AnyView {
let popover = popovers.count
return AnyView.init(
VStack(spacing: 64) {
Button("Close") {
withAnimation {
_ = popovers.removeLast()
}
}
.font(.largeTitle)
.padding()
Button("Add") {
withAnimation {
popovers += [new()]
}
}
.font(.largeTitle)
.padding()
Text("This is popover #\(popover)")
.font(.title)
.foregroundColor(.white)
.fixedSize()
}
.background(Color.init(hue: 0.65-(Double(3*popover)/100.0), saturation: 0.3, brightness: 0.9).opacity(0.98))
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension View {
var asAnyView : AnyView {
AnyView(self)
}
}
The solution is to add instead animation to container. Tested with Xcode 12 / iOS 14.
struct Popovers : View {
#State var popovers : [AnyView] = []
var body : some View {
Button("Add a view ...") {
withAnimation {
popovers += [new()]
}
}
.blur(radius: 0 < popovers.count ? 8 : 0)
.overlay(ZStack {
ForEach(0..<self.popovers.count, id: \.self) { i in
popovers[i]
.frame(maxWidth: .infinity, maxHeight: .infinity)
.blur(radius: (i+1) < popovers.count ? 8 : 0)
.transition(.move(edge: .trailing))
}
}.animation(.default)) // << add animation to container
}
func new() -> AnyView {
let popover = popovers.count
return AnyView.init(
VStack(spacing: 64) {
Button("Close") {
_ = popovers.removeLast()
}
.font(.largeTitle)
.padding()
Button("Add") {
popovers += [new()]
}
.font(.largeTitle)
.padding()
Text("This is popover #\(popover)")
.font(.title)
.foregroundColor(.white)
.fixedSize()
}
.background(Color.init(hue: 0.65-(Double(3*popover)/100.0), saturation: 0.3, brightness: 0.9).opacity(0.98))
)
}
}

swiftUI auto delay animation after 2sec

hi guys im learning swiftUI and i have some problem with my project.
i have one main card that will rotate 5 random card, plus the back of the card. and at the bottom 5 button that represent the 5 random card.
when i press any of the 5 buttons to rotate the card, i would like that the card rotate back automatically on the cardBack after 2 sec.
here is my code :
import SwiftUI
struct CardBack: View {
var body: some View {
Image("back_card")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
}
}
struct ContentView: View {
#State var flipped = false
#State private var cardsFront = ["bigCard1", "bigCard2", "bigCard3", "bigCard4", "bigCard5" ]
#State private var cardBack = "back_card"
var body: some View {
VStack {
Spacer()
ZStack {
Image(flipped ? self.cardsFront.randomElement()! : self.cardBack)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
.rotation3DEffect(Angle(degrees: flipped ? 180 : 0 ), axis: (x: 0, y: 1, z: 0))
}
Spacer()
HStack {
Button(action: {
withAnimation(.spring()) {
self.flipped.toggle()
}
}) {
Image("circle")
.renderingMode(.original)
}
Button(action: {
}) {
Image("plus")
.renderingMode(.original)
}
Button(action: {
}) {
Image("wave")
.renderingMode(.original)
}
Button(action: {
}) {
Image("square")
.renderingMode(.original)
}
Button(action: {
}) {
Image("star")
.renderingMode(.original)
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is a demo on one button
Button(action: {
withAnimation(.spring()) {
self.flipped.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation(.spring()) {
self.flipped.toggle()
}
}
}) {
Image("circle")
.renderingMode(.original)
}

Animating a View by its height in SwiftUI

I am attempting to make a view which will animate another content view in from the bottom of the screen. The below code works, however, as the content view will have unknown height the 200 offset may not be correct. How can I get the height of the content in order to offset the view correctly?
struct Test<Content>: View where Content : View {
#State var showing: Bool = false
var content: Content
var body: some View {
VStack {
Button(action: {
withAnimation {
self.showing.toggle()
}
}) {
Text("Toggle")
}
Spacer()
HStack {
Spacer()
content
Spacer()
}
.background(Color.red)
.padding(10)
.offset(y: showing ? 200 : 0)
}
}
}
Here is possible approach to read content height directly from it during alignment...
struct Test<Content>: View where Content : View {
var content: Content
#State private var showing: Bool = false
#State private var contentHeight: CGFloat = .zero
var body: some View {
VStack {
Button(action: {
withAnimation {
self.showing.toggle()
}
}) {
Text("Toggle")
}
Spacer()
HStack {
Spacer()
content
.alignmentGuide(VerticalAlignment.center) { d in
DispatchQueue.main.async {
self.contentHeight = d.height
}
return d[VerticalAlignment.center]
}
Spacer()
}
.background(Color.red)
.padding(10)
.offset(y: showing ? contentHeight : 0)
}
}
}

Resources