When using combine in SwiftUI to update a Lottie Animation, it fails to update at the right time and gives an unexpected result - swift

I have this ViewModel that uses combine along with a timer and I want this ViewModel to update a LottieView and filename with a new animation. When the timer counts down I want it to publish and send specific strings, these strings will be the json Lottie filenames. When my ContentView receives these filenames I want it to dynamically update the LottieViews animation.
So I made an #State variable called name inside ContentView and made that equal to the received value that is passed in. However, what confuses me is that the filename that is being published and sent from the ViewModels timer at the 10 second mark is suppose to be received and used inside LottieView(filename: name).
However this LottieView instantly runs this file when I launch the app. How so? The only place that filename exists in the whole app is when the timer reaches 10 seconds and it shouldn't even exist when LottieView(name) is called. It also ignores the previous file name that should have been run at the 19 second mark. If I were to ignore LottieView(name) all together and run a Text View instead, so in this case Text(name), when I run the app the Text properly changes when the timer reaches 10.
So how come LottieView(name) runs like this? I verified these files properly match their animations as well.
import SwiftUI
import Combine
class ViewModel: ObservableObject {
var anyCancellable: AnyCancellable?
let publish = PassthroughSubject<String, Never>()
private var timer: Timer?
private var scheduleTime = 20
init() {
fire()
anyCancellable = publish.sink { str in
print("Value that is being passed over: \(str)")
}
}
func fire() {
print("Fire timer")
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.scheduleTime -= 1
print(self.scheduleTime)
if self.scheduleTime == 19 {
self.publish.send("13865-sign-for-error-flat-style")
}
if self.scheduleTime == 10 {
self.publish.send("4174-unlock-to-premium")
timer.invalidate()
}
}
}
}
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var vm = ViewModel()
#State var name: String = ""
var body: some View {
VStack {
LottieView(filename: $name)
Text(name)
}
.onReceive(vm.publish, perform: { value in
print("passed over : \(value)")
name = value
print(name)
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
import Lottie
struct LottieView: UIViewRepresentable {
typealias UIViewType = UIView
#Binding var filename: String
func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
let view = UIView(frame: .zero)
let animationView = AnimationView()
let animation = Animation.named(filename)
animationView.animation = animation
animationView.contentMode = .scaleAspectFit
animationView.play()
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
NSLayoutConstraint.activate([
animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
animationView.heightAnchor.constraint(equalTo: view.heightAnchor),
animationView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
animationView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {
}
}

After a day of searching, without finding an answer, I probably came up with a not very correct solution, but it seems to work(saving temp fileName and after update LottieView checked name):
import SwiftUI
import Lottie
struct LottieView: UIViewRepresentable {
typealias UIViewType = UIView
var filename: String
var animationView = AnimationView()
let isPaused: Bool
var needUpdate: Bool = false {
didSet {
if needUpdate {
let animation = Animation.named(filename)
animationView.animation = animation
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .loop
animationView.play()
needUpdate = false
}
}
}
func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
let view = UIView(frame: .zero)
let animation = Animation.named(filename)
animationView.animation = animation
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .loop
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
NSLayoutConstraint.activate([
animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
animationView.heightAnchor.constraint(equalTo: view.heightAnchor),
])
animationView.play()
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {
let tempFileName = context.coordinator.parent.filename
DispatchQueue.main.async {
context.coordinator.parent.filename = filename
if tempFileName != filename {
context.coordinator.parent.needUpdate = true
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var parent: LottieView
init(_ parent: LottieView) {
self.parent = parent
}
}
}

Related

How to open a SwiftUI view by tapping an entity in RealityKit?

I am using SwiftUI with RealityKit. As displayed in the code below, I have a plane entity that when tapped simply prints the name of the entity. What approach should I take toward navigating to a new view when I tap the entity? It would be preferable to navigate as with a navigation link in a normal view, but if that is not possible then perhaps a fullScreenCover?
ARViewContainer.swift:
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
print(entity.name)
}
}
}
struct ARViewContainer: UIViewRepresentable {
typealias UIViewType = ARView
func makeUIView(context: Context) -> ARView{
let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)
context.coordinator.view = arView
arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
arView.scene.anchors.removeAll()
let anchor = AnchorEntity()
let plane = MeshResource.generatePlane(width: 1, height: 1)
var material = UnlitMaterial()
material.color = .init(tint: .white,
texture: .init(try! .load(named: "instagram")))
let planeEntity = ModelEntity(mesh: plane, materials: [material])
planeEntity.generateCollisionShapes(recursive: true)
planeEntity.name = "Plane Entity"
planeEntity.position.z -= 1.0
planeEntity.setParent(anchor)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context){
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
ContentView.swift
struct ContentView: View {
#State var open = false
var body: some View {
NavigationView{
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
}
}
}
}
View I want to navigate to:
struct TestView : View {
var body : some View {
VStack{
Text("Test View")
}
}
}
Manage the state of the view in an observable object and modify it from your AR view.
struct ContentView: View {
#ObservedObject var settings = Settings.shared
var body: some View {
NavigationView {
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
NavigationLink("", isActive: $settings.shouldOpenDetailsView) {
TestView()
}
}
}
}
}
class Settings: ObservableObject {
static let shared = Settings()
#Published var shouldOpenDetailsView = false
}
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
Settings.shared.shouldOpenDetailsView = true
}
}
}

Method `enumerateChildNodes` is not finding nodes

I'm developing an ARKit app that has 2 buttons: "Play" and "Reset". When I click on Play, a pyramid node is displayed. When I click on Reset, all pyramid nodes should be deleted from the scene:
Another requirement is that I want to disable the Play button when I click on it.
Next is my contentView code:
import ARKit
struct ContentView: View {
#State var node = SCNNode()
#State var play: Bool = false
#State var reset: Bool = false
#State var isDisabled: Bool = false
#State var showBanner:Bool = true
var body: some View {
ZStack {
SceneKitView(node: $node, play: $play, reset: $reset, isDisabled: $isDisabled)
VStack {
Spacer()
HStack {
Button(action: {
play = true
}) {
Image("Play")
}.padding()
.disabled(isDisabled) /// <<<<< THIS DISABLES THE BUTTON
Spacer()
Button(action: {
play = false
reset = true
}) {
Image("Reset")
}.padding()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In order to disable the Play button, I'm using a .disabled and the Boolean isDisabled.
My code changes the value of isDisabled to TRUE in a Coordinator :
import ARKit
import SwiftUI
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
let config = ARWorldTrackingConfiguration()
#Binding var node: SCNNode
#Binding var play: Bool
#Binding var reset: Bool
#Binding var isDisabled: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, ARSCNViewDelegate {
var control: SceneKitView
init(_ control: SceneKitView) {
self.control = control
}
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
DispatchQueue.main.async {
if self.control.play {
self.control.addNode()
self.control.play = false
self.control.isDisabled = true // HERE I UPDATE THE VALUE !!!!!!!!
self.control.reset = false
}else{
}
}
}
}
func removeNode(){
self.arView.scene.rootNode.enumerateChildNodes { (node, _) in
print("existing nodes = \(node)")
node.removeFromParentNode()
}
}
func updateUIView(_ uiView: ARSCNView,
context: Context) {
if self.reset {
self.removeNode()
print("game reseted")
}
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.delegate = context.coordinator
arView.autoenablesDefaultLighting = true
//arView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
arView.showsStatistics = true
arView.session.run(self.config)
return arView
}
func addNode(){
let pyramid = SCNNode(geometry: SCNPyramid(width: 0.1, height: 0.1, length: 0.1))
pyramid.geometry?.firstMaterial?.diffuse.contents = UIColor.red
pyramid.geometry?.firstMaterial?.specular.contents = UIColor.white
pyramid.position = SCNVector3(0,0,-0.5)
self.arView.scene.rootNode.addChildNode(pyramid)
}
}
Problem: When the app is running and I click on RESET, the method removeNode is invoked and this method uses enumerateChildNodes to find the nodes and delete them using removeFromParentNode but the pyramid node is not removed ! :(
The crazy thing (for me) is that if I don't change the value of isDisabled in the Coordinator, i.e. I comment that line, removeNode works, and the node is removed !!!!!
Any comment, suggestion is welcome :)
This solves the issue:
func updateUIView(_ uiView: ARSCNView, context: Context) {
if self.reset {
DispatchQueue.main.async {
context.coordinator.control.removeNode()
context.coordinator.control.isDisabled = false
}
print("Game Reset")
}
}

Trying to set #published bool to true based on results from an API call

Hi first off I'm very new to swift and programing (coming from design field).
I'm trying to update doesNotificationsExist based on posts.count
I'm getting true inside the Api().getPosts {}
Where I print the following:
print("Api().getPosts")
print(doesNotificationExist)
but outside (in the loadData() {}) I still get false and not the #Publihed var doesNotificationExist:Bool = false doesn't update.
Please help me out, I would really appreciate some guidance to what I'm doing wrong and what I need to do.
Here is my code:
import SwiftUI
import Combine
public class DataStore: ObservableObject {
#Published var posts: [Post] = []
#Published var doesNotificationExist:Bool = false
init() {
loadData()
startApiWatch()
}
func loadData() {
Api().getPosts { [self] (posts) in
self.posts = posts
if posts.count >= 1 {
doesNotificationExist = true
}
else {
doesNotificationExist = false
}
print("Api().getPosts")
print(doesNotificationExist)
}
print("loadData")
print(doesNotificationExist)
}
func startApiWatch() {
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {_ in
self.loadData()
}
}
View where I'm trying to set an image based on store.doesNotificationsExist
StatusBarController:
import AppKit
import SwiftUI
class StatusBarController {
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
#ObservedObject var store = DataStore()
init(_ popover: NSPopover)
{
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = statusBar.statusItem(withLength: 28.0)
statusItem.button?.action = #selector(togglePopover(sender:))
statusItem.button?.target = self
if let statusBarButton = statusItem.button {
let itemImage = NSImage(named: store.doesNotificationExist ? "StatusItemImageNotification" : "StatusItemImage")
statusBarButton.image = itemImage
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
}
}
`Other none relevant code for the question`
}
It’s a closure and hopefully the #escaping one. #escaping is used to inform callers of a function that takes a closure that the closure might be stored or otherwise outlive the scope of the receiving function. So, your outside print statement will be called first with bool value false, and once timer is completed closure will be called changing your Bool value to true.
Check code below -:
import SwiftUI
public class Model: ObservableObject {
//#Published var posts: [Post] = []
#Published var doesNotificationExist:Bool = false
init() {
loadData()
// startApiWatch()
}
func loadData() {
getPost { [weak self] (posts) in
//self.posts = posts
if posts >= 1 {
self?.doesNotificationExist = true
}
else {
self?.doesNotificationExist = false
}
print("Api().getPosts")
print(self?.doesNotificationExist)
}
print("loadData")
print(doesNotificationExist)
}
func getPost(completion:#escaping (Int) -> ()){
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) {_ in
completion(5)
}
}
}
struct Test1:View {
#ObservedObject var test = Model()
var body: some View{
Text("\(test.doesNotificationExist.description)")
}
}

Working in Swift: How to transfer variables between UIKit (ViewRepresentabble) and SwiftUI

I am new to swift and I am having treouble making my SwiftUI and UIkit pices of code talk to each other.
I have a variable in my swiftUI that I want to transfer to the UIkit piece or vice versa.
This is my swfitUI code and I want to pass someString to the UIKit code
struct ForceTestView: View {
//MARK: - PROPERTIES
//MARK: - BODY
var body: some View {
VStack(spacing: 20){
TouchesSwiftUI()
.border(Color.gray, width: 5)
.frame(maxWidth: 200, maxHeight: 200)
Text("\(someString)")
.onTapGesture {
someString = "New Value"
}
}//:VSTACK
}//: BODY
struct TouchesSwiftUI: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> TouchesHelper {
let touchControl = TouchesHelper()
return touchControl
}
func updateUIViewController(_ uiViewController: TouchesHelper, context: Context) {
}
}
This is the UIkit code that I have
import Foundation
import UIKit
import SwiftUI
import Combine
class TouchesHelper: UIViewController{
var forceLabel: UILabel!
var forceText: String = "frcTxt Empty"
var xPosLabel: UILabel!
override func loadView() {
view = UIView()
view.backgroundColor = .white
forceLabel = UILabel()
forceLabel.translatesAutoresizingMaskIntoConstraints = false
forceLabel.textAlignment = .left
forceLabel.font = UIFont.systemFont(ofSize: 18)
forceLabel.text = "Force Readings"
forceLabel.numberOfLines = 0
view.addSubview(forceLabel)
xPosLabel = UILabel()
xPosLabel.translatesAutoresizingMaskIntoConstraints = false
xPosLabel.textAlignment = .left
xPosLabel.font = UIFont.systemFont(ofSize: 18)
xPosLabel.text = "Finger X Position"
xPosLabel.numberOfLines = 0
view.addSubview(xPosLabel)
NSLayoutConstraint.activate([
forceLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
forceLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
xPosLabel.topAnchor.constraint(equalTo: forceLabel.bottomAnchor),
xPosLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let userForce = touch.force
forceLabel.text = "Force: \(userForce)"
forceText = "\(userForce)"
print("UI \(testString)")
}//:if touch
}//: touchesMoved
}
I would want to pass the userForce to swiftUI, and then update something like the userxPosLabel with the text input from SwiftUI
I have tried to look at #Obverbable objects and coordinators, but they don't really make sense or work because they don't help in passing things that are connected to touching events within TOuchesMoved (I am coding this for a project that uses an iPhoneX, which had 3D touch)
Any help will be really appreciated!
Thank you!
There are a couple of different ways you can approach the specifics of this, but your inclination to use an ObservableObject like you mentioned is a good one.
Code first, and then some explanation:
class ViewModel : ObservableObject {
#Published var force : String = ""
#Published var xPos : String = ""
}
struct ContentView : View {
#StateObject var viewModel = ViewModel()
//MARK: - BODY
var body: some View {
VStack(spacing: 20){
TouchesSwiftUI(viewModel: viewModel, force: viewModel.force, xPos: viewModel.xPos)
.border(Color.gray, width: 5)
.frame(maxWidth: 200, maxHeight: 200)
Text(viewModel.force)
.onTapGesture {
viewModel.force = "New Value"
}
Text(viewModel.xPos)
}//:VSTACK
}//: BODY
}
struct TouchesSwiftUI: UIViewControllerRepresentable {
var viewModel : ViewModel
var force: String
var xPos: String
func makeUIViewController(context: Context) -> TouchesHelper {
let touchControl = TouchesHelper()
return touchControl
}
func updateUIViewController(_ uiViewController: TouchesHelper, context: Context) {
uiViewController.viewModel = viewModel
uiViewController.forceLabel.text = force
uiViewController.xPosLabel.text = xPos
}
}
class TouchesHelper: UIViewController{
var viewModel : ViewModel?
// {
// didSet {
// guard let viewModel = viewModel else { return }
// cancellable = viewModel.objectWillChange.sink(receiveValue: { _ in
// DispatchQueue.main.async {
// forceLabel.text = viewModel.force
// xPosLabel.text = viewModel.
// }
// })
// }
// }
var forceLabel: UILabel!
var xPosLabel: UILabel!
private var cancellable : AnyCancellable?
override func loadView() {
view = UIView()
view.backgroundColor = .white
forceLabel = UILabel()
forceLabel.translatesAutoresizingMaskIntoConstraints = false
forceLabel.textAlignment = .left
forceLabel.font = UIFont.systemFont(ofSize: 18)
forceLabel.text = "Force Readings"
forceLabel.numberOfLines = 0
view.addSubview(forceLabel)
xPosLabel = UILabel()
xPosLabel.translatesAutoresizingMaskIntoConstraints = false
xPosLabel.textAlignment = .left
xPosLabel.font = UIFont.systemFont(ofSize: 18)
xPosLabel.text = "Finger X Position"
xPosLabel.numberOfLines = 0
view.addSubview(xPosLabel)
NSLayoutConstraint.activate([
forceLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
forceLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
xPosLabel.topAnchor.constraint(equalTo: forceLabel.bottomAnchor),
xPosLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let userForce = touch.force
//forceLabel.text = "Force: \(userForce)"
//xPosLabel.text = "X: \(touch.location(in: self.view).x)"
viewModel?.force = "\(userForce)"
viewModel?.xPos = "X: \(Int(touch.location(in: self.view).x))"
}//:if touch
}//: touchesMoved
}
What's happening:
ViewModel holds a shared state. This controls what's on the SwiftUI Text labels on ContentView and gets passed to the TouchesSwiftUI which in turn passes it to TouchesHelper
In touchesMoved, TouchesHelper updates ViewModel -- the changes in the labels end up getting propagated back through to TouchesHelper updateUIViewController
You can see that your onTapGesture affects the state as you would expect in both ContentView and TouchesHelper
I left a bit commented in TouchesHelper -- this is just to show that if you wanted to subscribe to updates via publishers on the ViewModel you could do that to change the label text rather than passing then back through updateUIViewController

swiftui dealloc and realloc AVplayer with many videos

I have many simultaneous videos. Through a Int var (var test1 and var test2) I would like to be able to add only a certain video and remove all the others so as not to have memory problems
As soon as the view is loaded, the value "nil" is assigned to each player and when test1 == test 2 it should load the video in a certain player and in the other "nil"
The problem is that despite being the testing variable in binding (struct VideoPlayer #Binding var testing) it does not update the state of the player which always remains in Nil
Below the best solution I have obtained so far
Some idea? Thank you all
struct CustomPlayer: View {
#Binding var test1:Int
#Binding var test2:Int
#State var path:String
#State var testing:AVPlayer? = nil
var body: some View {
if(test1 == test2 ) {
self.testing? = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "\(path)", ofType: "mp4")!) )
self.testing?.play()
} else {
self.testing?.replaceCurrentItem(with: nil)
}
return ZStack{
VideoPlayer(testing: self.$testing)
}
struct VideoPlayer : UIViewControllerRepresentable {
#Binding var testing : AVPlayer?
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = testing
controller.showsPlaybackControls = false
controller.view.backgroundColor = UIColor.white
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayer>) {
}
}
Here an example how to load multiple videos simultaneous (i added an autoplay feuature), and remove all other, but not the selected one.
To remove videos tap on video you want to save
And i'm not sure that i solve your initial problem, but this can give you a clue where to look next
Code can be copy/pasted, as i declare it in one file to convenient stackoverflow use
import SwiftUI
import AVKit
struct VideoModel: Identifiable {
let id: Int
let name: String
let type: String = ".mp4"
}
final class VideoData: ObservableObject {
#Published var videos = [VideoModel(id: 100, name: "video"),
VideoModel(id: 101, name: "wow"),
VideoModel(id: 102, name: "okay")]
}
//Multiple item player
struct MultipleVideoPlayer: View {
#EnvironmentObject var userData: VideoData
var body: some View {
VStack(alignment: .center, spacing: 8) {
ForEach(userData.videos) { video in
VideoPlayer(video: .constant(video))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
}
}
}
}
//Single item player
struct VideoPlayer : UIViewControllerRepresentable {
#EnvironmentObject var userData: VideoData
#Binding var video: VideoModel
func makeUIViewController(context: Context) -> AVPlayerViewController {
guard let path = Bundle.main.path(forResource: video.name, ofType: video.type) else {
fatalError("\(video.name)\(video.type) not found")
}
let url = URL(fileURLWithPath: path)
let playerItem = AVPlayerItem(url: url)
context.coordinator.player = AVPlayer(playerItem: playerItem)
context.coordinator.player?.isMuted = true
context.coordinator.player?.actionAtItemEnd = .none
NotificationCenter.default.addObserver(context.coordinator,
selector: #selector(Coordinator.playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: context.coordinator.player?.currentItem)
let controller = AVPlayerViewController()
controller.player = context.coordinator.player
controller.showsPlaybackControls = false
controller.view.backgroundColor = UIColor.white
controller.delegate = context.coordinator
let tapRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.playerDidTap))
controller.view.addGestureRecognizer(tapRecognizer)
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
uiViewController.player?.play()
}
func makeCoordinator() -> Coordinator {
let coord = Coordinator(self)
return coord
}
class Coordinator: NSObject, AVPlayerViewControllerDelegate {
var parent: VideoPlayer
var player: AVPlayer?
init(_ playerViewController: VideoPlayer) {
self.parent = playerViewController
}
#objc func playerItemDidReachEnd(notification: NSNotification) {
if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: CMTime.zero, completionHandler: nil)
}
}
#objc func playerDidTap(){
parent.userData.videos = parent.userData.videos.filter { videoItem in
return videoItem.id == parent.video.id
}
}
}
}
//preview
struct AnotherEntry_Previews: PreviewProvider {
static var previews: some View {
MultipleVideoPlayer()
}
}
And in 'SceneDelegate.swift' replace app entry point with
window.rootViewController = UIHostingController(rootView: MultipleVideoPlayer().environmentObject(VideoData()))
The key thing of this move is to have "VideoData" resource, you can achieve it with EnvironmentObject, or with some other shared data example
Videos below i added to project, including them to Target Membership of a project
#Published var videos = [VideoModel(id: 100, name: "video"),
VideoModel(id: 101, name: "wow"),
VideoModel(id: 102, name: "okay")]
Your code seems to be ok. Are you sure the AVPlayer(...) path is correct
and testing is not nil. Put
.....
self.testing?.play()
print("-----> testing: \(testing.debugDescription)")
is testing = nil at that point?