Hey guys I am still kind of new to Stackoverflow so please bear with me if I am doing something wrong, I am trying my best.
I am trying to make a simple app with the new apple watchOS6 and swiftUI in the new Xcode11 beta that plays a sound file from assets.xcassets.
I think I am doing something wrong when trying to find the file I have in the assets, here's the error code:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/phillipeismark/Desktop/WatchOS6/WatchOS6Demo/WatchOS6Demo WatchKit Extension/Model/AudioPlayer.swift, line 21
2019-08-28 15:06:58.396058+0200 WatchOS6Demo WatchKit Extension[12691:794523] Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/phillipeismark/Desktop/WatchOS6/WatchOS6Demo/WatchOS6Demo WatchKit Extension/Model/AudioPlayer.swift, line 21
My code is built like this:
I have a ContentView that is a list that just displays my button that calls the my audioplayer when its pressed. The code for ContentView is not included.
The AudioPlayerButton struct looks like this and it basically just calls play() on the player:
struct AudioPlayerButton: View {
#State var player = AudioPlayer()
var body: some View {
Button(action: player.play) {
Image("newPlayButton")
}
}
}
This is my AudioPlayer class where I get the error:
import Foundation
import AVFoundation
import Combine
import SwiftUI
class AudioPlayer {
var didChange = PassthroughSubject <Void, Never>()
var isPlaying: Bool = false
var AudioPlayer: AVAudioPlayer?
let url = URL.init(fileURLWithPath: Bundle.main.path(forResource: "Glad", ofType: ".wav")!)
//let url = URL.init(fileURLWithPath: Bundle.main.path(forResource: "Glad", ofType: ".wav", inDirectory:"/Users/phillipeismark/Desktop/WatchOS6/WatchOS6Demo/WatchOS6Demo WatchKit Extension/Assets.xcassets/" )!)
func play() {
do {
AudioPlayer = try AVAudioPlayer(contentsOf: url)
AudioPlayer?.play()
} catch {
print(error)
}
}
}
I noticed that there are multiple asset.xcassets folders, one inside watchOS6 WatchKit App and one inside of WatchOS6 WatchKit Extension, to be sure I hadn't put it in the wrong place I just added the file to both of the folders.
Here is what I have tried:
1: I noticed that there is multiple asset.xcassets folders and I have tried to add the file to both of them.
2: I have tried to set both the ForResource and ofType parameters as nil to kind of try to "hit more targets" as the Documentation says: "If you specify nil, the method returns the first resource file it finds that matches the remaining criteria".
3: I tried to give a value to the the 3rd parameter in path(forResource:ofType:inDirectory:) as you can see in my AudioPlayer class with the outcommented line.
4: I took an old sound from an old project that I knew worked. I also tested if that project could run and it could.
Anything that could send me in the right direction are very much appreciated! Thanks.
I uploaded the project to GitHub if anybody wanted to look at it.
Update: IT WORKS I ran the normal iPhone simulator in another project. I did not do anything but make a new project and start the simulator. Then I opened the browser went on YouTube and played a video. It still didn't work so I let the video play in the simulator and continued to google around. Then when the video ended and YouTube had to play an add the sound magically started to work - there was an add even before the video started but that one didn't make it work, it was at the videos end. I was speechless. my GUESS is that the iPhone simulator made a configuration but I have no idea. Any theories about how this work or could work is very much appreciated
Related
Our app uses universal links and the AASA file seems to be working fine because hyperlinks in emails, as well as QR coded URLs both launch the app. They both also cause the SwiftUI .onOpenURL(perform:) function to be called.
Things are a bit more confusing with NFC. The URL is recognised and the app launches, suggesting the issue is not related to AASA. However, the onOpenURL function is not called. Can anyone tell me how to handle universal links from NFC? Ideally I'd like to keep it pure SwiftUI but if I need to use the AppDelegate so be it.
The code in the main SwiftUI file is:
import SwiftUI
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
print("url opened:\(url)")
// prints when opened from URL in email,
// Notes or QR code, but not when opened
// from URL embedded in NFC NDEF
})
}
}
}
I have also tried defining the application(_:continue:restorationHandler:) function in the AppDelegate, but that doesn't get called either.
Thanks for any help.
The solution is to add a continue user activity handler on a suitable view:
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
print("Continue activity \(userActivity)")
guard let url = userActivity.webpageURL else {
return
}
print("User wants to open URL: \(url)")
// TODO same handling as done in onOpenURL()
}
It's not obvious why the same Universal Link received through a click on a link in Safari should be handled differently than the same link read from a tag, but apparently it's not the same thing.
In case it helps anyone with the same issue, I solved the issue by registering a URL schema for the app and then using .onOpenURL(perform:).
Seems a strange way to have to do it but it works as required, so happy days!
I am working on making a Xcode app using swift. I am trying to run FFMPEG when someone clicks on a button. In terminal it would look like
ffmpeg -i input.mp4 output.mp4
But after searching everywhere, I can't seem to find how to run a command as if you were typing it into Terminal.
So my question is, how do you run a command (probably from a textfield) just as if you typed it into Terminal?
Here is my code so far:
import Foundation
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var inURL: NSTextField!
#IBOutlet weak var inText: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func clickURL(_ sender: Any) {
var musicURL = inURL.stringValue
if musicURL.isEmpty {
musicURL = "No URL entered!"
}
let textReplace = "Downloading music from: \(musicURL)..."
inText.stringValue = textReplace
Process.launchedProcess(launchPath: "/usr/local/Cellar/ffmpeg/4.0.2/bin/ffmpeg", arguments: ["-i","input.mp4","output.mp4"])
}
}
I will also be using youtube-dl from GitHub, if I can get FFMPEG working.
var p = Process.launchedProcess(launchPath: "/a/path/to/ffmpeg", arguments: ["-i", "input.mp4", "output.mp4"])
I am also trying to do this at the moment and it seems to be a permission issue, has nothing to do with swift. I recently read it here. The idea is that sandboxed apps cannot read files from just anywhere. For some standard paths, you can enable access (see here), but for others you cannot. Also, seems like it is possible to launch executables from /usr/bin by default, but not for /usr/local/bin, at least not without specifically enabling it. I'll be looking into this some more and come back if I find a proper solution.
Edit:
An option that I just tested an works for me, is to bundle ffmpeg in my app sandbox and use it from there. I downloaded ffmpeg from here and added it to Xcode in the target Build Phases > Copy Bundle Resources, then accessed it programmatically from this path
let ffmpegPath = Bundle.main.bundlePath + "/Contents/Resources/ffmpeg"
I am working on accepting a CKShare in a macOS app in Swift 4. I've already done all the following:
Create the CKShare and save it with its rootRecord to CloudKit
Add a participant (CKShare.Participant)
I've confirmed that the CKShare is on the CloudKit server and that the person I invited has access to it. Here's a screenshot: https://d.pr/i/0sMFQq
When I click the share link associated with the CKShare, it opens my app, but nothing happens and userDidAcceptCloudKitShareWith doesn't fire.
func application(_ application: NSApplication, userDidAcceptCloudKitShareWith metadata: CKShareMetadata) {
print("Made it!") //<-- This never gets logged :(
let shareOperation = CKAcceptSharesOperation(shareMetadatas: [metadata])
shareOperation.qualityOfService = .userInteractive
shareOperation.perShareCompletionBlock = {meta, share, error in
print("meta \(meta)\nshare \(share)\nerror \(error)")
}
shareOperation.acceptSharesCompletionBlock = { error in
if let error = error{
print("error in accept share completion \(error)")
}else{
//Send your user to where they need to go in your app
print("successful share:\n\(metadata)")
}
}
CKContainer.default().add(shareOperation)
}
Is there some kind of URL scheme I have to include in my info.plist? Or perhaps a protocol I need to conform to in my NSApplicationDelegate delegate? I can't, for the life of me, figure out what to do. Thanks in advance!
Update
I've tried a few more things on this. When I open the share link in a web browser, I see this:
Clicking OK makes the screen fade away to this:
Not particularly helpful. :) After doing this, the participant's status in CloudKit is still Invited, so the share still hasn't been accepted.
When I click on a share link within Messages, I am shown a popup like this:
After I click open, a new copy of my app shows up in the dock, then the app suddenly closes. The crash log states:
Terminating app due to uncaught exception 'CKException', reason: 'The application is missing required entitlement com.apple.developer.icloud-services'
I've tried turning iCloud off and on again in the Capabilities section of Xcode, but nothing changes. I know this exception can't be right because I can start my app normally and use CloudKit all day long. Only the CKShare causes this crash.
This is a mess. Save me, Obi-wan Kenobi, you're my only hope.
Yes,
You need to add this to your info.plist.
<key>CKSharingSupported</key>
<true/>
** EDITED ANSWER **
I use this code to share, I don't do it manually... not sure if this is an option under OS X I must confess. I am using iOS.
let share = CKShare(rootRecord: record2S!)
share[CKShareTitleKey] = "My Next Share" as CKRecordValue
share.publicPermission = .none
let sharingController = UICloudSharingController(preparationHandler: {(UICloudSharingController, handler:
#escaping (CKShare?, CKContainer?, Error?) -> Void) in
let modifyOp = CKModifyRecordsOperation(recordsToSave:
[record2S!, share], recordIDsToDelete: nil)
modifyOp.savePolicy = .allKeys
modifyOp.modifyRecordsCompletionBlock = { (record, recordID,
error) in
handler(share, CKContainer.default(), error)
}
CKContainer.default().privateCloudDatabase.add(modifyOp)
})
sharingController.availablePermissions = [.allowReadWrite,
.allowPrivate]
sharingController.delegate = self
sharingController.popoverPresentationController?.sourceView = self.view
DispatchQueue.main.async {
self.present(sharingController, animated:true, completion:nil)
}
This presents an activity controller in which you can choose say email and then send a link. You might also want to watch this video, focus on cloudKit JS right at the beginning.
Watch this WWDC video too https://developer.apple.com/videos/play/wwdc2015/710/
It talks about the cloudkit JSON API, using it you can query what has and what hasn't been shared in a terminal window/simple script perhaps. I did the same when using dropbox API a few years back. Hey you can even use the cloudkit JSON API within your code in place of the native calls.
I finally got it to work! I did all of the following:
Deleted my app from ~/Library/Developer/Excode/DerivedData
Made sure I had no other copies of my app archived anywhere on my machine.
Said a prayer.
Rebooted.
Sheesh, that was rough. :)
If your app is a Mac Catalyst app running on any version of macOS Catalina at least up to and including 10.15.4 Beta 1, a UIApplicationDelegate userDidAcceptCloudKitShareWith method will never be invoked.
After some significant debugging, we discovered that the MacCatalyst UIKit doesn’t even have an implementation for userDidAcceptCloudKitShareWithMetadata in its UIApplication delegate. It’s not broken, it’s just not there. So, at least temporarily, our workaround is the following, which seems to work, even if it’s very inelegant:
// Add CloudKit sharing acceptance handling to UINSApplicationDelegate, which is missing it.
#if targetEnvironment(macCatalyst)
extension NSObject {
#objc func application(_ application: NSObject, userDidAcceptCloudKitShareWithMetadata cloudKitShareMetadata: CKShare.Metadata) {
YourClass.acceptCloudKitShare(cloudKitShareMetadata: cloudKitShareMetadata)
}
}
#endif
If you are using a SceneDelegate, implement the delegate callback there, instead of on AppDelegate.
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
// ...
}
You need to create the app delegate for your SwiftUI app using #NSApplicationDelegateAdaptor:
#main
struct Sharing_ServiceApp: App
{
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene
{
WindowGroup
{
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
I put that line in and my code instantly started receiving the share requests.
what am i doing wrong?
i'm trying to open a midi file and read it's contents. so far i can open the file in playgrounds but i get "Open::fopen failed" in my project
override func viewDidLoad() {
super.viewDidLoad()
let path = NSURL.fileURL(withPath: "/users/me/file.mid")
var midfile: MusicSequence?
NewMusicSequence(&midfile)
MusicSequenceFileLoad(midfile!, path as CFURL, .midiType , .smf_ChannelsToTracks)
}
i do have import AudioToolbox and CoreMIDI
EDIT: some progress
I'm able to open midi files from bundle with CFBundleCopyResourceURL, but i want to be able to open files from anywhere in finder
fileManager also fails, it returns nil when used inside a project and works in playgrounds.
figured it out.
code fails because the app runs sandboxed and the NSURL can't be accessed
I'm struggling to write my first Swift3 app - a screensaver because I can't reference any local files. After hours of debugging and setting up a Playground I found that the Bundle.main.bundlePath is not pointing at my app but rather "/Applications/Xcode.app". Which explains why in the code below - video.mp4 is never found but I get nil back from selecting the Bundle by identifier as well.
Any suggestions on how to fix this? My one local file (video.mp4) is readable, copied as a Bundle Resource and has no spurious xattrs. I'm stuck! Any help appreciated.
Running 10.12.3/Xcode 8.2.1, Swift 3.0.2
Playground code below:
import Cocoa
import PlaygroundSupport
import AVKit
import AVFoundation
var frameRect = NSRect(x: 0, y: 0, width: 600, height: 600)
var mainView = NSView(frame: frameRect)
let bundle = Bundle(identifier: "com.modprods.koorigras-test.Playground")
debugPrint(bundle)
debugPrint(Bundle.main.bundlePath)
guard let path = Bundle.main.path(forResource: "video",ofType:"mp4") else {
debugPrint("not found")
exit(1)
}
As per Eric's comment, this is correct behavior for a Playground.
For other n00bs, creating a new Playground inside your workspace results in greyed out Sources and Resources folders that if you try to show in finder nothing happens (because they don't exist). To access local files from within a new Playground
create the Resources subfolder
copy the asset to Resource
make sure Copy Bundle Resources includes this asset for the target
It appears there is no way of accessing other bundles from within a Playground.