I would like to make the star.png move to the left, return to the starting point, and move to the right repeatedly. I have tried to add .animation(Animation.linear.speed(0.1)), it worked, but it showed 'animation' was deprecated in iOS 15.0: Use withAnimation or animation(_:value:) instead.
Here's my code:
struct CloudView: View {
#State var show = true
#State private var scaleValue: CGFloat = 1
var body: some View {
HStack {
if show {
Image(uiImage: UIImage(named: "star.png")!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
.padding(.top, -670)
.offset(x: -270)
.transition(.move(edge: .leading))
.animation(Animation.linear.speed(0.05).repeatForever(autoreverses: false))
}
}
.onAppear {
withAnimation(.spring()) {
self.show.toggle()
}
self.show = true
}
}
}
How to avoid this caution?
You can just use the new animation(_:value:) to avoid the warning. Pretty much the new animation is applying the 'animation' to this Image view when it detects whatever you put as 'value' changes.
Apple's documentation on Animation
Related
Here's my code:
struct StarView: View {
#State var show = true
var body: some View {
HStack {
if show {
Image(uiImage: UIImage(named: "star.png")!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
.transition(.slide)
.onAppear {
withAnimation(.spring()) {
self.show.toggle()
}
}
}
}
}
}
Can the image be moved to the left instead the right?
How to prevent the image from disappearing after moving?
This makes the slide come in from the left and I just added a self.show = true below the withAnimation part to prevent it from being hidden.
You can then play around with where to put the self.show = true and make changes to the animation to get different results, if that's what you want. But this code solves the two things you mentioned.
struct StarView: View {
#State var show = true
var body: some View {
HStack {
if show {
Image(uiImage: UIImage(named "star.png")!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
.transition(.move(edge: .trailing))
.onAppear {
withAnimation(.spring()) {
self.show.toggle()
}
self.show = true
}
}
}
}
}
I'm still new to SwiftUI and have run into a problem. I need to use back and forward buttons to make the Video Player go to the previous/next video (stored locally). The following code works for one video only, the one declared into the init(), but I can't manage to change the video by clicking the back and forward buttons.
I'm using an array of String called videoNames to pass all the video names from the previous View.
Also, I'm using a custom Video Player for this and I'm gonna include the relevant parts of the code.
This is my View:
struct WorkingOutSessionView: View {
let videoNames: [String]
#State var customPlayer : AVPlayer
#State var isplaying = false
#State var showcontrols = false
init(videoNames: [String]) {
self.videoNames = videoNames
self._customPlayer = State(initialValue: AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoNames[0], ofType: "mov")!)))
}
var body: some View {
VStack {
CustomVideoPlayer(player: $customPlayer)
.frame(width: 390, height: 219)
.onTapGesture {
self.showcontrols = true
}
GeometryReader {_ in
// BUTTONS
HStack {
// BACK BUTTON
Button(action: {
// code
}, label: {
Image(systemName: "lessthan")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.foregroundColor((Color(red: 243/255, green: 189/255, blue: 126/255)))
.padding()
})
// FORWARD BUTTON
Button(action: {
// code
}, label: {
Image(systemName: "greaterthan")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.foregroundColor((Color(red: 243/255, green: 189/255, blue: 126/255)))
.padding()
})
}
}
}
.offset(y: 35)
.edgesIgnoringSafeArea(.all)
}
This is my custom Video Player:
struct CustomVideoPlayer : UIViewControllerRepresentable {
#Binding var player: AVPlayer
func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<CustomVideoPlayer>) {
}
}
I've researched for solutions but couldn't find anything relevant. I've tried to modify my CustomVideoPlayer and not pass a #Binding variable... As well, the init() gave me a lot of headaches as it comes back to errors every time I change something...
Any solution would help guys. I really appreciate your time.
The first thing that you'll need is something to keep track of your position in the video list. I'm using another #State variable for this.
Whenever that state variable changes, you'll need to update your player. I'm using the onChange modifier near the bottom of the code to do this work.
In your CustomVideoPlayer, you need to use updateUIViewController to make sure the player is up-to-date with the parameter being passed in.
Lastly, there's no need for AVPlayer to be a #Binding, since it's a class that is passed by reference, not a struct that is passed by value.
struct WorkingOutSessionView: View {
let videoNames: [String]
#State private var customPlayer : AVPlayer
#State private var currentItem = 0
#State var isplaying = false
#State var showcontrols = false
init(videoNames: [String]) {
self.videoNames = videoNames
self._customPlayer = State(initialValue: AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoNames[0], ofType: "mov")!)))
}
var body: some View {
VStack {
CustomVideoPlayer(player: customPlayer)
.frame(width: 390, height: 219)
.onTapGesture {
self.showcontrols = true
}
.onAppear {
self.customPlayer.play()
}
GeometryReader { _ in
// BUTTONS
HStack {
// BACK BUTTON
Button(action: {
currentItem = min(currentItem, currentItem - 1)
}) {
Image(systemName: "lessthan")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.foregroundColor((Color(red: 243/255, green: 189/255, blue: 126/255)))
.padding()
}
// FORWARD BUTTON
Button(action: {
currentItem = min(videoNames.count - 1, currentItem + 1)
}) {
Image(systemName: "greaterthan")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.foregroundColor((Color(red: 243/255, green: 189/255, blue: 126/255)))
.padding()
}
}
}
}
.offset(y: 35)
.edgesIgnoringSafeArea(.all)
.onChange(of: currentItem) { currentItem in
print("Going to:",currentItem)
self.customPlayer.pause()
self.customPlayer = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoNames[currentItem], ofType: "mov")!))
self.customPlayer.play()
}
}
}
struct CustomVideoPlayer : UIViewControllerRepresentable {
var player: AVPlayer
func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<CustomVideoPlayer>) {
uiViewController.player = player
}
}
If this were my own project, I'd probably continue to do some refactoring -- maybe move some things to a view model, etc. Also, I'd probably avoid initializing an AVPlayer in a View's init as I mentioned in my last answer to you on your previous question. It works, but there's definitely a risk you'll end up doing too much heavy lifting if the view hierarchy re-renders.
This code shows the general behavior I'm trying to achieve, but how can I make this continuous so there's no apparent end to the image, with no gaps of white space and a smooth transition? AND - isn't there a better way to do this???
Thanks!
import SwiftUI
struct ContentView: View {
#State private var xVal: CGFloat = 0.0
#State private var timer = Timer.publish(every: 0.05, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
Image("game_background_2") //image attached
.aspectRatio(contentMode: .fit)
.offset(x: xVal, y: 0)
.transition(.slide)
.padding()
.onReceive(timer) {_ in
xVal += 2
if xVal == 800 { xVal = 0 }
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here's a simple approach that could work. First use a GeometryReader to set the frame of the screen. Then add an HStack with 2 images inside. The first image is full size and the 2nd image is limited to the width of the screen. Finally, animate the HStack to move from the .trailing to the .leading edge of the screen frame. We set the animation .repeatForever so that it continues loops and autoReverses: false so that it restarts. When it restarts, however, it should be seamless because the restart position is identical to the 2nd image.
struct ImageBackgroundView: View {
#State var animate: Bool = false
let animation: Animation = Animation.linear(duration: 10.0).repeatForever(autoreverses: false)
var body: some View {
GeometryReader { geo in
HStack(spacing: -1) {
Image("game_background_2")
.aspectRatio(contentMode: .fit)
Image("game_background_2")
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width, alignment: .leading)
}
.frame(width: geo.size.width, height: geo.size.height,
alignment: animate ? .trailing : .leading)
}
.ignoresSafeArea()
.onAppear {
withAnimation(animation) {
animate.toggle()
}
}
}
}
I try to make a smooth animation when the NetStatus change but it's not working like i want.
I want to get the same effect as when i press the button with the toggle animation. The commented button animation is working great and i try to replicate it with the scaling of the height of the text frame.
The commented button code is just for a working example of the animation effect that i want (expand and close gracefully), i don't need this code.
How can i do that?
import SwiftUI
struct NoNetwork: View {
let screenSize: CGRect = UIScreen.main.bounds
#ObservedObject var online = NetStatus()
var body: some View {
VStack{
Text("NoNetworkTitle")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: self.online.connected ? 0 : 40, alignment: .center)
// .animation(.easeIn(duration: 5))
.background(Color.red)
// Button(action: {
// withAnimation {
// self.online.connected.toggle()
// }
// }, label: {
// Text("Animate")
// })
}
}
}
struct NoNetwork_Previews: PreviewProvider {
static var previews: some View {
NoNetwork()
}
}
To animate when online.connected changes, put the .animation modifier on the VStack:
VStack{
Text("NoNetworkTitle")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: self.online.connected ? 0 : 40, alignment: .center)
.background(Color.red)
Button(action: {
self.online.connected.toggle()
}, label: {
Text("Animate")
})
}
.animation(.easeInOut(duration: 0.5))
This will animate the other views in the VStack as the Text appears and disappears.
I need to make an Alert in SwiftUI that has an editable TextField in it. Currently, this isn't supported by SwiftUI (as of Xcode 11.3), so I'm looking for a work-around.
I know I can implement by wrapping the normal UIKit bits in a UIHostingController, but really want to stick with an all-SwiftUI implementation.
I've got two VStacks in a ZStack, with the front one (the one with the TextView) being hidden and disabled until until you tap the button. Take a look at this:
import SwiftUI
struct ContentView: View {
#State var isShowingEditField = false
#State var text: String = "12345"
var body: some View {
ZStack {
VStack {
Text("Value is \(self.text)")
Button(action: {
print("button")
self.isShowingEditField = true
}) {
Text("Tap To Test")
}
}
.disabled(self.isShowingEditField)
.opacity(self.isShowingEditField ? 0.25 : 1.00)
VStack(alignment: .center) {
Text("Edit the text")
TextField("", text: self.$text)
.multilineTextAlignment(.center)
.lineLimit(1)
Divider()
HStack {
Button(action: {
withAnimation {
self.isShowingEditField = false
print("completed... value is \(self.text)")
}
}) {
Text("OK")
}
}
}
.padding()
.background(Color.white)
.shadow(radius: CGFloat(1.0))
.disabled(!self.isShowingEditField)
.opacity(self.isShowingEditField ? 1.0 : 0.0)
}
}
}
This seems like it should work to me. Switching between the two VStacks works well, but the TextField is not editable.
It acts like it's disabled, but it's not. Explicitly .disabled(false) to the TextField doesn't help. Also, it should already be enabled anyway since 1) that's the default, 2) the VStack it's in is specifically being set as enabled, and 3) The OK button works normally.
Ideas/workarounds?
Thanks!
You need to force the TextField updating with some methods. Like the following:
TextField("", text: self.$text)
.multilineTextAlignment(.center)
.lineLimit(1)
.id(self.isShowingEditField)
That is really ridiculous, but the problem disappears if you comment just one line of code:
//.shadow(radius: CGFloat(1.0))
I commented it and everything works. I think you need to use ZStack somewhere in your custom alert view to avoid this.. hm, bug, maybe?
update
try some experiments. If you leave just this code in that View:
struct CustomAlertWithTextField: View {
#State var isShowingEditField = false
#State var text: String = "12345"
var body: some View {
TextField("", text: $text)
.padding()
.shadow(radius: CGFloat(1.0))
}
}
it would not work again. only if you comment .padding() or .shadow(...)
BUT if you relaunch Xcode - this code begin to work (which made me crazy). Tried it at Xcode Version 11.2 (11B52)
update 2 the working code version:
struct CustomAlertWithTextField: View {
#State var isShowingEditField = false
#State var text: String = "12345"
var body: some View {
ZStack {
VStack {
Text("Value is \(self.text)")
Button(action: {
print("button")
self.isShowingEditField = true
}) {
Text("Tap To Test")
}
}
.disabled(self.isShowingEditField)
.opacity(self.isShowingEditField ? 0.25 : 1.00)
ZStack {
Rectangle()
.fill(Color.white)
.frame(width: 300, height: 100)
.shadow(radius: 1) // moved shadow here, so it doesn't affect TextField now
VStack(alignment: .center) {
Text("Edit the text")
TextField("", text: self.$text)
.multilineTextAlignment(.center)
.lineLimit(1)
.frame(width: 298)
Divider().frame(width: 300)
HStack {
Button(action: {
withAnimation {
self.isShowingEditField = false
print("completed... value is \(self.text)")
}
}) {
Text("OK")
}
}
}
}
.padding()
.background(Color.white)
.disabled(!self.isShowingEditField)
.opacity(self.isShowingEditField ? 1.0 : 0.0)
}
}
}
after some research i solved this problem by adding .clipped() to the VStack.
For your problem, it would look like this:
VStack(alignment: .center) {
Text("Edit the text")
TextField("", text: self.$text)
.multilineTextAlignment(.center)
.lineLimit(1)
Divider()
HStack {
Button(action: {
withAnimation {
self.isShowingEditField = false
print("completed... value is \(self.text)")
}
}) {
Text("OK")
}
}
}
.padding()
.clipped() // <- here
.background(Color.white)
.shadow(radius: CGFloat(1.0))
.disabled(!self.isShowingEditField)
.opacity(self.isShowingEditField ? 1.0 : 0.0)