Play local sound on watchOS button tap - swift

I'm new to swift development and building a simple app for the apple watch. I'd like a short sound to play when a button is tapped. Is this possible?
From what I understand the best way to do this is with AVFoundation and AVAudioPlayer. There seem to have been a lot of updates for this in the last few releases and I'm finding conflicting advice. Based on a few tutorials I've put this simple test together, but I'm getting a "Thread 1:Fatal error: Unexpectedly found nil when unwrapping an Optional value" Here is my code:
import WatchKit
import Foundation
import AVFoundation
var dogSound = AVAudioPlayer()
class InterfaceController: WKInterfaceController {
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func playTapped() {
let path = Bundle.main.path(forResource: "Dog", ofType: ".mp3")!
let url = URL(fileURLWithPath: path)
do {
dogSound = try AVAudioPlayer(contentsOf: url)
dogSound.play()
} catch {
//couldn't load file :(
}
}
}
My audio file is an mp3 named Dog in the Assets.xcassets folder in WatchKit Extension and I have added AVFoundation.framework to the Link Binary with Libraries
What am I doing wrong and is there a tutorial for the right way to implement this? Thanks a lot!

Related

Can I use the Snapchat SDK (SnapKit) with SwiftUI?

I'm trying to integrate Snapkit with an iOS app but I want to use SwiftUI instead of UIKit. I've already done the required setup with Snapkit and now I'm trying to get the snapchat login button to display in my app. I know the Snapkit SDK is made for UIKit and not SwiftUI, but SwiftUI has a way to wrap UIViews into SwiftUI with the UIViewRepresentable protocol. I've tried implementing this but the login button still doesn't display.
Here's my code:
import SwiftUI
import UIKit
import SCSDKLoginKit
struct ContentView: View {
var body: some View {
SnapchatLoginButtonView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct SnapchatLoginButtonView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> SCSDKLoginButton {
let s = SCSDKLoginButton()
s.delegate = context.coordinator
return s
}
func updateUIView(_ uiView: SCSDKLoginButton, context: Context) {
}
class Coordinator: NSObject, SCSDKLoginButtonDelegate {
func loginButtonDidTap() {
}
}
}
I have a feeling I'm missing something from within SCSDKLoginButton, but not sure what it is so here's the file SCSDKLoginButton.h for reference. Any help would be greatly appreciated!
//
// SCSDKLoginButton.h
// SCSDKLoginKit
//
// Copyright © 2018 Snap, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#protocol SCSDKLoginButtonDelegate
- (void)loginButtonDidTap;
#end
#interface SCSDKLoginButton : UIView
#property (nonatomic, weak, nullable) id<SCSDKLoginButtonDelegate> delegate;
- (instancetype)initWithCompletion:(nullable void (^)(BOOL success, NSError *error))completion NS_DESIGNATED_INITIALIZER;
#end
Coincidentally I attempted implementing the SnapKit SDK in an exclusive SwiftUI/ iOS13 project about 3 days after you posted your Issue.
Unfortunately I can't directly resolve your issue as there are a few key issues which Snapchat has to address with their SDK before it is suitable for development with the SceneDelegate & AppDelegate Paradigm introduced in iOS 13. But I hope I can shed light on your question and present my findings to anyone else who is in a similar predicament.
These are the following issues/ observations I made on my quest of Implementing SCSDKLoginKit & SCSDKBitmojiKit in SwiftUI:
The most basic issue is that the SCSDKLoginKit Module is outdated, As you correctly realised. SCSDKLoginClient.login() requires the calling view to conform to the (UIKIT) UIViewController class. So we must use the workaround with a UIViewControllerRepresentable to act as our SwiftUI <-> UIKit intermediary.
However the fundamental issue relates to the fact that the SnapKit SDK documentation has not been updated to give developers a SceneDelegate link between Snapchat Auth and Your app's logic. So even if you implemented your SCSDKLoginButton correctly it is not smooth sailing!
Now to directly answer your question, You are attempting to wrap a SCSDKLoginButton in a UIViewControllerRepresentable which can be done and I'm sure someone with better knowledge of coordinators etc than myself can help you with that. However I just wanted to show that your efforts at the moment may be fruitless until snapchat provides an updated SDK.
Here is my setup:
[ContentView.swift]
import SwiftUI
struct ContentView: View {
#State private var isPresented = false
var body: some View {
Button("Snapchat Login Button") { self.isPresented = true}
.sheet(isPresented: $isPresented) {
LoginCVWrapper()
}
}
}
[LoginCVWrapper.swift]
import SwiftUI
import UIKit
import SCSDKLoginKit
struct LoginCVWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return LoginViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
//Unused in demonstration
}
}
[LoginViewController.swift]
import UIKit
import SCSDKLoginKit
class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
performLogin() //Attempt Snap Login Here
}
//Snapchat Credential Retrieval Fails Here
private func performLogin() {
//SCSDKLoginClient.login() never completes once scene becomes active again after Snapchat redirect back to this app.
SCSDKLoginClient.login(from: self, completion: { success, error in
if let error = error {
print("***ERROR LOC: manualTrigger() \(error.localizedDescription)***")
return
}
if success {
self.fetchSnapUserInfo({ (userEntity, error) in
print("***SUCCESS LOC: manualTrigger()***")
if let userEntity = userEntity {
DispatchQueue.main.async {
print("SUCCESS:\(userEntity)")
}
}
})
}
})
}
private func fetchSnapUserInfo(_ completion: #escaping ((UserEntity?, Error?) -> ())){
let graphQLQuery = "{me{displayName, bitmoji{avatar}}}"
SCSDKLoginClient
.fetchUserData(
withQuery: graphQLQuery,
variables: nil,
success: { userInfo in
if let userInfo = userInfo,
let data = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted),
let userEntity = try? JSONDecoder().decode(UserEntity.self, from: data) {
completion(userEntity, nil)
}
}) { (error, isUserLoggedOut) in
completion(nil, error)
}
}
}
[This Runs as follows]:
GIF: Code Running On Device
More on the SceneDelegate interface link issue:
When you inevitably implement the SCSDKLoginClient.login() call (presumably when your SCSDKLoginButton is pressed), Snapchat will open, it will present the "grant access" sheet correctly assuming your app is linked in the Snapchat Dev Portal.
When you accept these permissions, Snapchat redirects to your application. However this is where the link between your app and retrieving a snapchat username/ bitmoji will breakdown. This is due to the fact that in new iOS 13 apps, the SceneDelegate handles when your application states change not the AppDelegate as in pre iOS13 versions. Therefore Snapchat returns the user data but your app never retrieves it.
[Going Forward]
The SnapKitSDK (currently version 1.4.3) needs to be updated along with the documentation.
I have just submitted a support question to Snapchat asking when this update will come so I will update this if I hear more. Apologies if you were looking for a direct solution to your SCSDKLoginButton() issue, I just wanted you to know what challenges lie beyond it at this current moment in time.
[Further Reading]
Facebook has updated their tools and documentation to incorporate the Scene/App Delegates. See step "5. Connect Your App Delegate and Scene Delegate" here: https://developers.facebook.com/docs/facebook-login/ios/
#Stephen2697 was right in pointing out that the snap sdk isn't built for iOS 13 yet due to SceneDelegate now handling oauth redirects rather than AppDelegate. I figured out a workaround to use the SCSDKLoginClient.application() method (which was made for appdelegate) to be used in scene delegate. Here's the code, add it to your scene delegate and the completion handler passed in to your Snapchat login will run:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for urlContext in URLContexts {
let url = urlContext.url
var options: [UIApplication.OpenURLOptionsKey : Any] = [:]
options[.openInPlace] = urlContext.options.openInPlace
options[.sourceApplication] = urlContext.options.sourceApplication
options[.annotation] = urlContext.options.annotation
SCSDKLoginClient.application(UIApplication.shared, open: url, options: options)
}
}
As far as the login button not appearing, make sure to add a completion handler to the SCSDKLoginButton instance or else it won't work. Like this:
let s = SCSDKLoginButton { (success, error) in
//handle login
}
To follow up with #Stephen.Alger's answer, you don't have to use UIViewControllerRepresentable just call: SCSDKLoginClient.login directly from SwiftUI's Button callback closure and add this modifier to the view:
.onOpenURL(perform: { url in
let success = SCSDKLoginClient.application(UIApplication.shared, open: url)
if success {
print("Logged in")
}
})

How to embed and stream a video from the web within an Xcode macOS Cocoa Application?

I am trying to get my macOS Cocoa Application Xcode project to play some video when I feed it a specified URL.
To simplify the question lets just use an empty Xcode project.
I want to achieve the same level of control as I am able to get using AVPlayerViewController() in my iOS Single View Application. Within that app I am currently using the solution offered here.
However, only the second example works in my macOS Cocoa Application when adapted like so:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer?.addSublayer(playerLayer)
player.play()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
This option however has 2 downsides:
It creates a repetitive error message in the console:
2017-06-27 18:22:06.833441+0200 testVideoOnmacOS[24456:1216773] initWithSessionInfo: XPC connection interrupted
2017-06-27 18:22:06.834830+0200 testVideoOnmacOS[24456:1216773] startConfigurationWithCompletionHandler: Failed to get remote object proxy: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.rtcreportingd" UserInfo={NSDebugDescription=connection to service named com.apple.rtcreportingd}
This AVPlayer solution does not offer the same control as the AVPlayerViewController() solution.
So I attempted to get the following AVPlayerViewController() solution to work. But it fails:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The error I am getting is:
Use of unresolved identifier 'AVPlayerViewController' - Did you mean
'AVPlayerViewControlsStyle'?
I have tried to work around this in many different ways. It will be confusing if I spell them all out. My prominent problem seems to be that the vast majority of online examples are iOS based.
Thus, the main question:
How would I be able to successfully use AVPlayerViewController within my Xcode macOS Cocoa Application?
Yes, got it working!
Thnx to ninjaproger's comment.
This seems to work in order to get a video from the web playing / streaming on a macOS Cocoa Application in Xcode.
First, create a AVKitPlayerView on your storyboard and link it as an IBOutlet to your NSViewController.
Then, simply apply this code:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
#IBOutlet var videoView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = videoView
playerViewController?.player = player
playerViewController?.player!.play()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Update august 2021
Ok, it's been a while since this answer has been up and I am no longer invested in it. But it seems others still end up here and the solution needs a bit more work to work properly at times.
With thanks to #SouthernYankee65:
What is needed sometimes is to go to the project file and on the Signing & Capabilities tab check Outgoing Connection (Client). Then in the info.plist add App Transport Security Settings dictionary property, then add Allows Arbitrary Loads boolean property and set it to YES.
The video now plays. However, some errors in the console might occur.

Xcode: how to create instances of views and pass info to them?

I'm trying to create a MacOS app that plays audio or video files. I've followed the simple instructions on Apple's website here
But I want to use the File > Open menu items to bring up an NSOpenPanel, and pass that to the View Controller.
So presumably, the Open action should be in the AppDelegate, as the ViewController window might not be open.
And then pass the filename to a new instance of the ViewController window.
Is that right? If so, how do I "call" the View from AppDelegate?
Here's the AppDelegate:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBAction func browseFile(sender: AnyObject) {
let dialog = NSOpenPanel();
if (dialog.runModal() == NSModalResponseOK) {
let result = dialog.url // Pathname of the file
if (result != nil) {
// Pass the filepath to the window view thing.
} else {
// User clicked on "Cancel"
return
}
}
}
and here's the ViewController:
class ViewController: NSViewController {
#IBOutlet weak var playerView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the URL somehow
let player = AVPlayer(url: url)
playerView.player = player
}
There are some details not disclosed in your question, but I believe I can provide the proper answer still.
You can call NSOpenPanel from AppDelegate, nothing wrong with that. Just note that user may cancel the dialog and how to handle that situation.
Considering the view the best thing is to create WindowController that is connected to the ViewController (it is like that by default) in the Storyboard, then access it from the code using NSStoryBoard.instantiateController(withIdentifier:), and then use its window property with something like window.makeKeyAndOrderFront(self) . If you have NSWindow or NSWindowController class in your code then you should initialize the class in the code and again make window key and front.

class "ViewController' has no initializers [duplicate]

This question already has answers here:
Class 'ViewController' has no initializers in swift
(8 answers)
Closed 6 years ago.
Im trying to build a simply sound app. I want a button press to play a clip pretty much like a soundboard. problem is when I go to build it says class "ViewController' has no initializers
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func playSound(sender:UIButton){
// Set the sound file name & extention
let audioFilePath = Bundle.main.path(forResource: "emp_learntoknow", ofType: "mp3")
if audioFilePath != nil {
let audioFileUrl = URL.init(fileURLWithPath: audioFilePath!)
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFileUrl)
audioPlayer.play()
} catch {
print("audio file is not found")
}
}
}
Because it has no initializers. You have a property:
var audioPlayer: AVAudioPlayer
...but you are not initializing it. You have, of course, given it a type, but a type is not a value. All properties must have initial values.
Like Matt has mentioned, it's happening because the property is not initialized.
In Swift, you have to use optionals for variables that could be nil at any point. You'll have to either initialize audioPlayer from the beginning or use an optional type, including the implicitly unwrapped optional if you know it will be set later on and stay that way.

Handle browser's Notification in Swift webview application

I have very little knowledge about Swift, xCode or Objective-C but I have read/watch a lot of tutorials and my only intention is to create a webview with notification in webkit wrapper such as this Cocoa application provided by xCode.
Currently I have my code that can pretty much run when I build it.
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var webView: WebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://irccloud.com"
self.webView.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: urlString)!))
}
override func webView(sender: WebView!, runJavaScriptAlertPanelWithMessage message: String!, initiatedByFrame frame: WebFrame!) {
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Now, if I access irccloud url directly I'm able to receive both sound and banner notification, but I'm wondering how can I make this notification able to display like native Mac app written in Swift?
This app able to do that, but it's in Objective-C https://github.com/jnordberg/irccloudapp