Using Swift5.3.2, iOS14.4.1, Xcode12.4,
I am trying to make two simultaneous gestures work in SwiftUI.
The child-View at question is a VideoPlayer.
The parent-View is a ZStack.
I am using the .simultaneousGesture modifier, hoping that this will allow all child-View gestures to still go through.
But they don't !
The following code works but does not allow the VideoPlayer's control gestures to be recognised.
In fact, the restartScannerTapGesture TapGesture in my example does always kick in - and the VideoPlayer control gestures (like pause, play, stop etc.) are unfortunately ignored.
Any idea on how to get child-View gestures work when added a gesture to a parent-View in Swift UI ??
struct ParentView: View {
@EnvironmentObject var myURLList
var body: some View {
let restartScannerTapGesture = TapGesture(count: 1)
.onEnded {
actionClickID = UUID()
}
ZStack {
if let url = URL(fileURLWithPath: myURLList[0].path){
if url.containsImage {
Image(uiImage: UIImage(contentsOfFile: url.path)!)
.resizable()
.scaledToFit()
.onAppear() {
isVideo = false
}
} else if url.containsVideo {
CustomPlayerView(url: url, isVideo: $isVideo)
.onAppear() {
isVideo = true
}
} else {
Text(LocalizedStringKey("MediaNotRecognizedKey"))
.multilineTextAlignment(.center)
.padding()
.onAppear() {
isVideo = false
}
}
} else {
Text(LocalizedStringKey("MediaNotRecognizedKey"))
.multilineTextAlignment(.center)
.padding()
.onAppear() {
isVideo = false
}
}
}
}
.simultaneousGesture(restartScannerTapGesture)
}
And here is the CustomPlayerView that shows the Video controls in the first place. It clearly is the child-View at question. Its gestures should also work, but they don't. Why ??
import SwiftUI
import AVKit
class PlayerViewModel: ObservableObject {
@Published var avPlayer: AVPlayer?
func loadFromUrl(url: URL) {
avPlayer = AVPlayer(url: url)
}
func playVideo() {
avPlayer?.play()
}
func stopVideo() {
avPlayer?.pause()
avPlayer?.replaceCurrentItem(with: nil)
}
}
struct CustomPlayerView: View {
var url : URL
@Binding var isVideo: Bool
@StateObject private var playerViewModel = PlayerViewModel()
var body: some View {
ZStack {
if let url = URL(fileURLWithPath: list.paths[index]){
if url.containsImage {
Image(uiImage: UIImage(contentsOfFile: url.path)!)
.resizable()
.scaledToFit()
.onAppear() {
isVideo = false
}
} else if url.containsVideo {
CustomPlayerView(url: url, isVideo: $isVideo)
.onAppear() {
isVideo = true
}
} else {
Text(LocalizedStringKey("MediaNotRecognizedKey"))
.multilineTextAlignment(.center)
.padding()
.onAppear() {
isVideo = false
}
}
} else {
Text(LocalizedStringKey("MediaNotRecognizedKey"))
.multilineTextAlignment(.center)
.padding()
.onAppear() {
isVideo = false
}
}
}
}
And the needed extension:
extension URL {
func mimeType() -> String {
let pathExtension = self.pathExtension
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as NSString, nil)?.takeRetainedValue() {
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
return mimetype as String
}
}
return "application/octet-stream"
}
var containsImage: Bool {
let mimeType = self.mimeType()
guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeRetainedValue() else {
return false
}
return UTTypeConformsTo(uti, kUTTypeImage)
}
var containsAudio: Bool {
let mimeType = self.mimeType()
guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeRetainedValue() else {
return false
}
return UTTypeConformsTo(uti, kUTTypeAudio)
}
var containsVideo: Bool {
let mimeType = self.mimeType()
guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeRetainedValue() else {
return false
}
return UTTypeConformsTo(uti, kUTTypeMovie)
}
}