ios怎么用spotify_在iOS中通过Spotify进行身份验证

2023-05-16

ios怎么用spotify

Authenticating through an API like Spotify in order to access and modify a user’s information, music, follows, and so on can be so confusing especially for beginners. Why you may ask? If you have little to no knowledge about dealing with headers, SDK, web APIs, tweaking Xcode’s Build Settings and Info.plist then you are in the right place. My goal in this article is to improve Spotify’s Authorization Guide with better visuals, clearer step by step, actual Swift codes, and more importantly, have my readers level up.

通过诸如Spotify之类的API进行身份验证以访问和修改用户的信息,音乐,跟随等内容可能会造成混乱,尤其是对于初学者而言。 你为什么会问? 如果您几乎不了解处理标头,SDK,Web API,调整Xcode的Build Settings和Info.plist,那么您来对地方了。 本文的目的是通过更好的视觉效果,更清晰的逐步操作,实际的Swift代码,以及更重要的是提高读者的水平来改进Spotify的授权指南。

我们去取得它 (Let’s get it)

I will go over refreshable authorization and authentication process in Swift using Spotify’s iOS SDK. This is a task all software engineers should be comfortable with especially for a very secure backend like Spotify.

我将使用Spotify的iOS SDK讨论Swift中可刷新的授权和身份验证过程。 这是所有软件工程师都应该适应的任务,尤其是对于像Spotify这样的非常安全的后端。

准备环境 (Prepare Your Environment)

I will assume that you registered your app and have a client Identifier from Spotify’s Developer Dashboard. If not, read through Spotify’s iOS SDK Quick Start and App Settings guide. It is important to have appName:// whitelisted as a redirect URIs in your project in Spotify Dashboard in order for Spotify to know how to go back to your app.

我将假设您已经注册了应用程序,并且具有Spotify开发人员仪表板中的客户端标识符。 如果没有,请仔细阅读Spotify的iOS SDK快速入门和应用设置指南。 重要的是,在Spotify信息中心的项目中将appName://作为重定向URI列入白名单,以便Spotify知道如何返回到您的应用程序。

在Xcode中设置Spotify的iOS SDK (Setup Spotify’s iOS SDK in Xcode)

Skip this section if you are not planning to use Spotify’s iOS SDK. If you want to make your user’s experience as delightful as possible, download the iOS SDK from GitHub and add it to your Xcode project.

如果您不打算使用Spotify的iOS SDK,请跳过本节。 如果您想使用户的体验尽可能愉悦,请从GitHub下载iOS SDK ,并将其添加到您的Xcode项目中。

Create a new file, header file, and name it like so AppName-Bridging-Header. Then replace all its contents with#import <SpotifyiOS/SpotifyiOS.h>. Your project navigator should look like the image below. Do not worry about the GoogleService-Info.plist file unless you have pods installed.

创建一个新文件,头文件,并命名为AppName-Bridging-Header 。 然后将其所有内容替换为#import <SpotifyiOS/SpotifyiOS.h> 。 您的项目导航器应如下图所示。 除非您已安装Pod,否则不必担心GoogleService-Info.plist文件。

设置-ObjC链接器标志 (Set -ObjC Linker Flag)

In order to support the iOS SDK and compile the Objective-C code contained inside the iOS SDK, we will need to add -Objc linker flag. In Xcode, add the linker flag like the image below.

为了支持iOS SDK并编译iOS SDK中包含的Objective-C代码,我们需要添加-Objc链接器标志。 在Xcode中,添加链接器标志,如下图所示。

添加桥接标题 (Add Bridging Header)

Then we need to add a bridging header next that will allow us to include Objective-C binaries inside our Swift app. We can do that by searching Objective-C Bridging Header in Build Settings and settings its value the same as the name of our header file like the image below

然后,我们需要添加一个桥接标头,该标头将使我们能够在Swift应用程序中包含Objective-C二进制文件。 我们可以通过在“构建设置”中搜索“ Objective-C桥接头”并将其值设置为与我们的头文件的名称相同的方式来做到这一点,如下图所示

确认信息(Confirm Info)

You will also need to add a new URL Types in Info tab. Give the identifier as your Bundle Id, and the value as your callback without the :// like the image below.

您还需要在“信息”选项卡中添加新的URL类型。 给出标识符作为您的Bundle ID,并给值作为不带://回调, ://图所示。

Lastly for security purposes, you will need to open Info.plist as source code and make sure you tell NSAppTransportSecurity that you are supporting the domain, spotify.com. Take this time to also make sure that you have the same changes on your Info.plist as mine that are marked with blue horizontal lines.

最后,出于安全性考虑,您需要打开Info.plist作为源代码,并确保告知NSAppTransportSecurity您正在支持域Spotify.com。 请花一些时间确保在Info.plist上所做的更改与用蓝色水平线标记的更改相同。

授权流程 (Authorization Flows)

Spotify comes with four flows to obtain app authorization. Those are:

Spotify附带四个流程来获取应用程序授权。 那些是:

  • Refreshable user authorization: Authorization Code Flow

    可刷新的用户授权:授权码流程

  • Refreshable user authorization: Authorization Code Flow With Proof Key for Code Exchange (PKCE)

    可刷新的用户授权:具有用于代码交换(PKCE)的证明密钥的授权代码流

  • Temporary user authorization: Implicit Grant

    临时用户授权:隐式授予

  • Refreshable app authorization: Client Credentials Flow

    可刷新的应用授权:客户端凭据流

In this article, we will be following the second option, Authorization Code Flow With Proof Key for Code Exchange (PKCE). According to Spotify, authorization code flow with PKCE is the best option for mobile and desktop applications because it is unsafe to store client secret. It also provides your app with an access token that can be refreshed.

在本文中,我们将采用第二种选择,即带有代码交换证明密钥的授权代码流(PKCE) 。 根据Spotify的说法,带有PKCE的授权码流是移动和桌面应用程序的最佳选择,因为它不安全地存储客户端机密。 它还为您的应用程序提供了可以刷新的访问令牌。

编码时间 (Time to Code)

It’s time to finally code. My code mostly came from one of Spotify’s iOS SDK Demo Projects, SPTLoginSampleAppSwift.

现在是时候开始编写代码了。 我的代码主要来自Spotify的iOS SDK演示项目之一SPTLoginSampleAppSwift 。

From Authorization Code Flow With Proof Key for Code Exchange (PKCE) it is telling us to 1. Create the code verifier challenge then 2. Construct the authorization URI. Fortunately for us, since we are using the Spotify iOS SDK, we can complete those two steps by initiating a session with our session manager. Simply call the following method on button tap.

带有代码交换证明密钥(PKCE)的授权代码流中,我们告诉我们1.创建代码验证者质询,然后2.构造授权URI 。 对我们来说幸运的是,由于我们使用的是Spotify iOS SDK,因此可以通过与会话管理器启动会话来完成这两个步骤。 只需在按钮点击上调用以下方法。

  @objc func didTapConnect(_ button: UIButton) {
guard let sessionManager = sessionManager else { return }
if #available(iOS 11, *) {
// Use this on iOS 11 and above to take advantage of SFAuthenticationSession
sessionManager.initiateSession(with: scopes, options: .clientOnly)
} else {
// Use this on iOS versions < 11 to use SFSafariViewController
sessionManager.initiateSession(with: scopes, options: .clientOnly, presenting: self)
}
}

The next step is 3. Your app redirects the user to the authorization URI. After initiating a session with session manager, our Spotify app will be launched to get permissions specified in scopes. If user accepts, we will get the code we need to get our access token.

下一步是3。您的应用将用户重定向到授权URI 。 与会话管理器启动会话后,将启动我们的Spotify应用以获取在范围中指定的权限。 如果用户接受,我们将获得获取访问令牌所需的code

The last thing we need to do is 4. Exchange the authorization code for an access token. To do that we will need to make a POST request to https://accounts.spotify.com/api/token endpoint with the following body (client_id, grant_type, code, redirect_uri) filled out.

我们需要做的最后一件事是4.交换访问令牌的授权代码。 为此,我们需要向https://accounts.spotify.com/api/token端点发出POST请求,并填写以下正文(client_id,grant_type,代码,redirect_uri)。

///fetch Spotify access token. Use after getting responseTypeCode
func fetchSpotifyToken(completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "https://accounts.spotify.com/api/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let spotifyAuthKey = "Basic \((spotifyClientId + ":" + spotifyClientSecretKey).data(using: .utf8)!.base64EncodedString())"
request.allHTTPHeaderFields = ["Authorization": spotifyAuthKey, "Content-Type": "application/x-www-form-urlencoded"]
do {
var requestBodyComponents = URLComponents()
let scopeAsString = stringScopes.joined(separator: " ") //put array to string separated by whitespace
requestBodyComponents.queryItems = [URLQueryItem(name: "client_id", value: spotifyClientId), URLQueryItem(name: "grant_type", value: "authorization_code"), URLQueryItem(name: "code", value: responseTypeCode!), URLQueryItem(name: "redirect_uri", value: redirectUri.absoluteString), URLQueryItem(name: "code_verifier", value: codeVerifier), URLQueryItem(name: "scope", value: scopeAsString),]
request.httpBody = requestBodyComponents.query?.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data, // is there data
let response = response as? HTTPURLResponse, // is there HTTP response
(200 ..< 300) ~= response.statusCode, // is statusCode 2XX
error == nil else { // was there no error, otherwise ...
print("Error fetching token \(error?.localizedDescription ?? "")")
return completion(nil, error)
}
let responseObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
print("Access Token Dictionary=", responseObject ?? "")
completion(responseObject, nil)
}
task.resume()
} catch {
print("Error JSON serialization \(error.localizedDescription)")
}
}

According to the guide, the exchange requires code_verifier to be included in the body, however, at the time of this writing, it is not required since we are using the iOS SDK. It may be required for web API authorization and authentication flow.

根据指南,交换要求将code_verifier包含在主体中,但是在撰写本文时,由于我们使用的是iOS SDK,因此不需要。 Web API授权和身份验证流程可能需要它。

ViewController.swift before authenticating, playing current music, pause current music.
进行身份验证,播放当前音乐,暂停当前音乐之前的ViewController.swift。

而已! (That’s it!)

Congratulations on successfully doing an authorization and authenticating using Spotify iOS SDK. You have leveled up in iOS development 👏🏼👏🏼👏🏼👏🏼👏🏼

祝贺您使用Spotify iOS SDK成功完成了授权和身份验证。 您已升级iOS开发development

挑战 (Challenge)

You may still be a little lost on what is going on, but that’s okay. Repetition is key to grasp these concepts. I highly suggest you do the challenge I have below.

您可能对所发生的事情仍然有些迷茫,但这没关系。 重复是掌握这些概念的关键。 我强烈建议您做下面的挑战。

  • Complete this by finishing the flow and access user’s information with the access token and requesting a refreshed access token.

    通过完成流程并使用访问令牌访问用户信息请求刷新的访问令牌来完成此操作

  • Try authorization and authentication using Facebook’s iOS SDK

    使用Facebook的iOS SDK尝试授权和身份验证

完整的源代码(Full Source Code)

For the full source code, click here.

有关完整的源代码,请单击此处。

//Constants.swiftimport Foundationlet accessTokenKey = "access-token-key"
let redirectUri = URL(string:"previewtify://")!
let spotifyClientId = "e9d953c9eff4433cb30acf3e4866a68d"
let spotifyClientSecretKey = "e891fd17090d4841afaf88c5730419a9"/*
Scopes let you specify exactly what types of data your application wants to access, and the set of scopes you pass in your call determines what access permissions the user is asked to grant. For more information, see https://developer.spotify.com/web-api/using-scopes/.
*///remove scopes you don't need
let
scopes: SPTScope = [.userReadEmail, .userReadPrivate,
.userReadPlaybackState, .userModifyPlaybackState,
.userReadCurrentlyPlaying, .streaming, .appRemoteControl,
.playlistReadCollaborative, .playlistModifyPublic, .playlistReadPrivate, .playlistModifyPrivate,
.userLibraryModify, .userLibraryRead,
.userTopRead, .userReadPlaybackState, .userReadCurrentlyPlaying,
.userFollowRead, .userFollowModify,]//remove scopes you don't need
let
stringScopes = ["user-read-email", "user-read-private",
"user-read-playback-state", "user-modify-playback-state", "user-read-currently-playing",
"streaming", "app-remote-control",
"playlist-read-collaborative", "playlist-modify-public", "playlist-read-private", "playlist-modify-private",
"user-library-modify", "user-library-read",
"user-top-read", "user-read-playback-position", "user-read-recently-played",
"user-follow-read", "user-follow-modify",]

SceneDelegate.swift

SceneDelegate.swift

import UIKitclass SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
lazy var rootViewController = ViewController() func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: UIScreen.main.bounds)
window!.makeKeyAndVisible()
window!.windowScene = windowScene
window!.rootViewController = rootViewController
}//for spotify authorization and authentication flow
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
let parameters = rootViewController.appRemote.authorizationParameters(from: url)
if let code = parameters?["code"] {
rootViewController.responseTypeCode = code
} else if let access_token = parameters?[SPTAppRemoteAccessTokenKey] {
rootViewController.accessToken = access_token
} else if let error_description = parameters?[SPTAppRemoteErrorDescriptionKey] {
print("No access token error =", error_description)
}
} func sceneDidBecomeActive(_ scene: UIScene) {
if let accessToken = rootViewController.appRemote.connectionParameters.accessToken {
rootViewController.appRemote.connectionParameters.accessToken = accessToken
rootViewController.appRemote.connect()
} else if let accessToken = rootViewController.accessToken {
rootViewController.appRemote.connectionParameters.accessToken = accessToken
rootViewController.appRemote.connect()
}
} func sceneWillResignActive(_ scene: UIScene) {
if rootViewController.appRemote.isConnected {
rootViewController.appRemote.disconnect()
}
}
}

ViewController.swift

ViewController.swift

import UIKitclass ViewController: UIViewController {

var responseTypeCode: String? {
didSet {
fetchSpotifyToken { (dictionary, error) in
if
let error = error {
print("Fetching token request error \(error)")
return}
let accessToken = dictionary!["access_token"] as! String
DispatchQueue.main.async {
self.appRemote.connectionParameters.accessToken = accessToken
self.appRemote.connect()
}
}
}
} lazy var appRemote: SPTAppRemote = {
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.connectionParameters.accessToken = self.accessToken
appRemote.delegate = self
return
appRemote
}() var accessToken = UserDefaults.standard.string(forKey: accessTokenKey) {
didSet {
let defaults = UserDefaults.standard
defaults.set(accessToken, forKey: accessTokenKey)
}
} lazy var configuration: SPTConfiguration = {
let configuration = SPTConfiguration(clientID: spotifyClientId, redirectURL: redirectUri)
// Set the playURI to a non-nil value so that Spotify plays music after authenticating and App Remote can connect
// otherwise another app switch will be required
configuration.playURI = ""
// Set these url's to your backend which contains the secret to exchange for an access token
// You can use the provided ruby script spotify_token_swap.rb for testing purposes
configuration.tokenSwapURL = URL(string: "http://localhost:1234/swap")
configuration.tokenRefreshURL = URL(string: "http://localhost:1234/refresh")
return configuration
}() lazy var sessionManager: SPTSessionManager? = {
let manager = SPTSessionManager(configuration: configuration, delegate: self)
return manager
}() private var lastPlayerState: SPTAppRemotePlayerState? // MARK: - Subviews private lazy var connectLabel: UILabel = {
let label = UILabel()
label.text = "Connect your Spotify account"
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textColor = UIColor(red:(29.0 / 255.0), green:(185.0 / 255.0), blue:(84.0 / 255.0), alpha:1.0)
label.translatesAutoresizingMaskIntoConstraints = false
return
label
}() private lazy var connectButton: UIButton = {
let button = UIButton()
button.backgroundColor = UIColor(red:(29.0 / 255.0), green:(185.0 / 255.0), blue:(84.0 / 255.0), alpha:1.0)
button.translatesAutoresizingMaskIntoConstraints = falsebutton.contentEdgeInsets = UIEdgeInsets(top: 11.75, left: 32.0, bottom: 11.75, right: 32.0)
button.layer.cornerRadius = 20.0
button.setTitle("Continue with Spotify", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold)
button.sizeToFit()
button.addTarget(self, action: #selector(didTapConnect(_:)), for: .touchUpInside)
return button
}() private lazy var disconnectButton: UIButton = {
let button = UIButton()
button.backgroundColor = UIColor(red:(29.0 / 255.0), green:(185.0 / 255.0), blue:(84.0 / 255.0), alpha:1.0)
button.translatesAutoresizingMaskIntoConstraints = falsebutton.contentEdgeInsets = UIEdgeInsets(top: 11.75, left: 32.0, bottom: 11.75, right: 32.0)
button.layer.cornerRadius = 20.0
button.setTitle("Sign out", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold)
button.sizeToFit()
button.addTarget(self, action: #selector(didTapDisconnect(_:)), for: .touchUpInside)
return button
}() private lazy var pauseAndPlayButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(didTapPauseOrPlay), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = falsebutton.sizeToFit()
return button
}() private lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = falseimageView.contentMode = .scaleAspectFit
return imageView
}() private lazy var trackLabel: UILabel = {
let trackLabel = UILabel()
trackLabel.translatesAutoresizingMaskIntoConstraints = falsetrackLabel.textColor = .black
trackLabel.font = UIFont.systemFont(ofSize: 18, weight: .medium)
trackLabel.textAlignment = .center
return trackLabel
}() //MARK: App Life Cycle
override
func viewDidLoad() {
super.viewDidLoad()
setupViews()
} override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateViewBasedOnConnected()
} //MARK: Methods
func
setupViews() {
view.backgroundColor = UIColor.white
view.addSubview(connectLabel)
view.addSubview(connectButton)
view.addSubview(disconnectButton)
view.addSubview(imageView)
view.addSubview(trackLabel)
view.addSubview(pauseAndPlayButton)
let constant: CGFloat = 16.0
connectButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = trueconnectButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = truedisconnectButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truedisconnectButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = trueconnectLabel.centerXAnchor.constraint(equalTo: connectButton.centerXAnchor).isActive = trueconnectLabel.bottomAnchor.constraint(equalTo: connectButton.topAnchor, constant: -constant).isActive = trueimageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = trueimageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = trueimageView.bottomAnchor.constraint(equalTo: trackLabel.topAnchor, constant: -constant).isActive = truetrackLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truetrackLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: constant).isActive = truetrackLabel.bottomAnchor.constraint(equalTo: connectLabel.topAnchor, constant: -constant).isActive = truepauseAndPlayButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truepauseAndPlayButton.topAnchor.constraint(equalTo: trackLabel.bottomAnchor, constant: constant).isActive = truepauseAndPlayButton.widthAnchor.constraint(equalToConstant: 50).isActive = truepauseAndPlayButton.heightAnchor.constraint(equalToConstant: 50).isActive = trueupdateViewBasedOnConnected()
} func update(playerState: SPTAppRemotePlayerState) {
if lastPlayerState?.track.uri != playerState.track.uri {
fetchArtwork(for: playerState.track)
}
lastPlayerState = playerState
trackLabel.text = playerState.track.name
if playerState.isPaused {
pauseAndPlayButton.setImage(UIImage(named: "play"), for: .normal)
} else {
pauseAndPlayButton.setImage(UIImage(named: "pause"), for: .normal)
}
} func updateViewBasedOnConnected() {
if appRemote.isConnected == true {
connectButton.isHidden = truedisconnectButton.isHidden = falseconnectLabel.isHidden = trueimageView.isHidden = falsetrackLabel.isHidden = falsepauseAndPlayButton.isHidden = false} else { //show login
disconnectButton.isHidden = trueconnectButton.isHidden = falseconnectLabel.isHidden = falseimageView.isHidden = truetrackLabel.isHidden = truepauseAndPlayButton.isHidden = true}
} func fetchArtwork(for track: SPTAppRemoteTrack) {
appRemote.imageAPI?.fetchImage(forItem: track, with: CGSize.zero, callback: { [weak self] (image, error) in
if
let error = error {
print("Error fetching track image: " + error.localizedDescription)
} else if let image = image as? UIImage {
self?.imageView.image = image
}
})
} func fetchPlayerState() {
appRemote.playerAPI?.getPlayerState({ [weak self] (playerState, error) in
if
let error = error {
print("Error getting player state:" + error.localizedDescription)
} else if let playerState = playerState as? SPTAppRemotePlayerState {
self?.update(playerState: playerState)
}
})
} // MARK: - Actions @objc func didTapPauseOrPlay(_ button: UIButton) {
if let lastPlayerState = lastPlayerState, lastPlayerState.isPaused {
appRemote.playerAPI?.resume(nil)
} else {
appRemote.playerAPI?.pause(nil)
}
} @objc func didTapDisconnect(_ button: UIButton) {
if appRemote.isConnected == true {
appRemote.disconnect()
}
} @objc func didTapConnect(_ button: UIButton) {
guard let sessionManager = sessionManager else { return }
if #available(iOS 11, *) {
// Use this on iOS 11 and above to take advantage of SFAuthenticationSession
sessionManager.initiateSession(with: scopes, options: .clientOnly)
} else {
// Use this on iOS versions < 11 to use SFSafariViewController
sessionManager.initiateSession(with: scopes, options: .clientOnly, presenting: self)
}
}// MARK: - Private Helpers private func presentAlertController(title: String, message: String, buttonTitle: String) {
DispatchQueue.main.async {
let controller = UIAlertController(title: title, message: message, preferredStyle: .alert)
let action = UIAlertAction(title: buttonTitle, style: .default, handler: nil)
controller.addAction(action)
self.present(controller, animated: true)
}
} //MARK: POST Request///fetch Spotify access token. Use after getting responseTypeCode
func fetchSpotifyToken(completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "https://accounts.spotify.com/api/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let spotifyAuthKey = "Basic \((spotifyClientId + ":" + spotifyClientSecretKey).data(using: .utf8)!.base64EncodedString())"
request.allHTTPHeaderFields = ["Authorization": spotifyAuthKey, "Content-Type": "application/x-www-form-urlencoded"]
do {
var requestBodyComponents = URLComponents()
let scopeAsString = stringScopes.joined(separator: " ") //put array to string separated by whitespace
requestBodyComponents.queryItems = [URLQueryItem(name: "client_id", value: spotifyClientId), URLQueryItem(name: "grant_type", value: "authorization_code"), URLQueryItem(name: "code", value: responseTypeCode!), URLQueryItem(name: "redirect_uri", value: redirectUri.absoluteString), URLQueryItem(name: "code_verifier", value: codeVerifier), URLQueryItem(name: "scope", value: scopeAsString),]
request.httpBody = requestBodyComponents.query?.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data, // is there data
let response = response as? HTTPURLResponse, // is there HTTP response
(200 ..< 300) ~= response.statusCode, // is statusCode 2XX
error == nil else { // was there no error, otherwise ...
print("Error fetching token \(error?.localizedDescription ?? "")")
return completion(nil, error)
}
let responseObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
print("Access Token Dictionary=", responseObject ?? "")
completion(responseObject, nil)
}
task.resume()
} catch {
print("Error JSON serialization \(error.localizedDescription)")
}
}
}
// MARK: - SPTAppRemoteDelegate
extension
ViewController: SPTAppRemoteDelegate {
func appRemoteDidEstablishConnection(_ appRemote: SPTAppRemote) {
updateViewBasedOnConnected()
appRemote.playerAPI?.delegate = selfappRemote.playerAPI?.subscribe(toPlayerState: { (success, error) in
if
let error = error {
print("Error subscribing to player state:" + error.localizedDescription)
}
})
fetchPlayerState()
} func appRemote(_ appRemote: SPTAppRemote, didDisconnectWithError error: Error?) {
updateViewBasedOnConnected()
lastPlayerState = nil} func appRemote(_ appRemote: SPTAppRemote, didFailConnectionAttemptWithError error: Error?) {
updateViewBasedOnConnected()
lastPlayerState = nil}
}
// MARK: - SPTAppRemotePlayerAPIDelegate
extension
ViewController: SPTAppRemotePlayerStateDelegate {
func playerStateDidChange(_ playerState: SPTAppRemotePlayerState) {
debugPrint("Spotify Track name: %@", playerState.track.name)
update(playerState: playerState)
}
}// MARK: - SPTSessionManagerDelegate
extension
ViewController: SPTSessionManagerDelegate {
func sessionManager(manager: SPTSessionManager, didFailWith error: Error) {
if error.localizedDescription == "The operation couldn’t be completed. (com.spotify.sdk.login error 1.)" {
print("AUTHENTICATE with WEBAPI")
} else {
presentAlertController(title: "Authorization Failed", message: error.localizedDescription, buttonTitle: "Bummer")
}
}

func sessionManager(manager: SPTSessionManager, didRenew session: SPTSession) {
presentAlertController(title: "Session Renewed", message: session.description, buttonTitle: "Sweet")
} func sessionManager(manager: SPTSessionManager, didInitiate session: SPTSession) {
appRemote.connectionParameters.accessToken = session.accessToken
appRemote.connect()
}
}

Thank you so much for reading my article on how to get authorization and authenticate using a Spotify SDK.

非常感谢您阅读我有关如何使用Spotify SDK获得授权和身份验证的文章。

翻译自: https://medium.com/@samuelfolledo/authenticate-with-spotify-in-ios-ae6612ecca91

ios怎么用spotify

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ios怎么用spotify_在iOS中通过Spotify进行身份验证 的相关文章

  • 将语音添加到自定义 UIMenuController

    我创建了一个自定义UIMenuController in a UIWebView但它似乎摆脱了 说出选择 选项UIMenuController在那之后 所有测试设备上的 偏好设置 中都打开了发言选择选项 并且它出现在其他应用程序中 包括非
  • iOS NSURLSession,如何在didCompleteWithError中重试

    我想在我的服务器上尝试一次调用 直到成功为止 我想每 30 秒尝试一次 所以我使用 NSURLSession 进行通话 NSURLSessionDownloadTask task self session downloadTaskWithR
  • 如何获取 UITableView 中的所有单元格

    假设我有一个包含多行的 UITableView 我想在某个时间点将所有 UITableViewCells 作为 NSArray 获取 我努力了 tableView visibleCells 但这种方法有一个问题 我无法拥有当前不在当前屏幕中
  • 我如何从 iPhone 设备获取电子邮件历史记录..?

    friends 我想从我的 iPhone 访问电子邮件历史记录 并且还希望在收到新邮件时收到通知 如果可能的话 请向我提供源代码片段 Thanks 简而言之 使用任何已记录的 API 都是不可能的
  • 将捕获的图像精确裁剪为 AVCaptureVideoPreviewLayer 中的外观

    我有一个使用 AV Foundation 的照片应用程序 我使用 AVCaptureVideoPreviewLayer 设置了一个预览层 它占据了屏幕的上半部分 因此 当用户尝试拍照时 他们只能看到屏幕上半部分看到的内容 这很好用 但是当用
  • 断点条件错误

    我已经根据条件设置了断点 event name isEqualToString Some Name 这很好用 但是 当我尝试添加另一个带有条件的断点时 part name isEqualToString Some Value With A
  • 由于 2.23 导致 iOS 应用程序被拒绝 - iOS 数据存储指南

    以下是 Apple 关于拒绝的消息 2 23 应用程序必须遵循 iOS 数据存储指南 否则将被拒绝 2 23 详情 在启动和内容下载时 您的应用程序会存储 6 5 MB 这并不意味着 遵守 iOS 数据存储指南 下一步 请验证只有用户使用您
  • 使用 nib 作为带有 nib 类的表节标题

    我想创建一个加载 nib 文件并将其设置为标题 UIView 的节标题 这个 nib 文件还将有一个关联的类 其中插座和操作连接到 因此我想像平常一样使用 nib 加载该类 我在网上搜索并找到了几个类似的答案 但我找不到任何适合我的答案 经
  • 在回调函数中调用目标c函数

    如何在回调函数中调用目标c函数 回调函数 static OSStatus inputRenderCallback void inRefCon AudioUnitRenderActionFlags ioActionFlags const Au
  • 在 UITextView 中获取 HTML

    我在中显示htmlUITextView by self textView setValue b Content b forKey contentToHTMLString 编辑内容后UITextView 我想获取包含 html 的内容 所以我
  • SceneKit unproject Z 文档解释?

    我正在经历一些 SceneKit 概念 而我试图在脑海中巩固的一个概念是 unprojectPoint 我知道该函数将获取 2D 中的一个点并返回 3D 中的一个点 因此具有正确的 Z 值 当我阅读文档时 我读到了以下内容 method u
  • 如何编辑应用程序包中的文件?

    在我的应用程序中 我从存储在捆绑资源中的 CSV 文件加载数据 但是 我希望能够在用户点击 更新 按钮时以编程方式更新此文件 有没有办法以编程方式更改应用程序包中的资源 这是我用来访问该文件的代码 NSString path NSBundl
  • iOS Swift 在后台下载大量小文件

    在我的应用程序中 我需要下载具有以下要求的文件 下载大量 例如 3000 个 小 PNG 文件 例如 5KB 逐个 如果应用程序在后台继续下载 如果图像下载失败 通常是因为互联网连接丢失 请等待 X 秒然后重试 如果失败Y次 则认为下载失败
  • 是否可以使用UIPageControl来控制UITableView的移动?

    从Apple示例 PageControl 中我们可以知道UIPageControl可以用来控制scrollview中页面的移动 由于 UITableView 是 UIScrollView 的子类 我想使用 UIPageControl 来控制
  • 在情节提要中将 Segue 拖至自身

    我想将一个 Segue 从我的视图控制器拖到其自身 所以我可以推送该特定视图控制器的 无限 实例 我知道如何在代码中执行此操作 即以编程方式实例化视图控制器 但是 我想尽可能使用 segues 我发现了一些在故事板中进行自我延续的 技巧 但
  • TTTAttributedLabel 可点击截断标记

    我有一个 TTTAttributedLabel 并为其指定了一个自定义属性截断标记 NSAttributedString atributedTruncationToken NSAttributedString alloc initWithS
  • UILabel 中的文本未垂直居中

    我使用以下代码创建了一个标签 func setupValueLabel valueLabel numberOfLines 1 valueLabel font UIFont name Avenir Black size 50 valueLab
  • 如何在Sprite Kit中实现鼠标关节?

    我已经在 iOS 上用 Cocos2d Box2d 编写了拖放功能的工作实现 我需要将它移植到 Sprite Kit 逻辑非常基本 当用户触摸屏幕时 找到手指下的精灵 在找到的精灵和场景的物理体之间创建鼠标关节 将关节的目标设置为触摸位置
  • 如何在 iOS 上固定证书的公钥

    在提高我们正在开发的 iOS 应用程序的安全性时 我们发现需要对服务器的 SSL 证书 全部或部分 进行 PIN 操作以防止中间人攻击 尽管有多种方法可以做到这一点 但当您搜索此内容时 我只找到了固定整个证书的示例 这种做法会带来一个问题
  • 当 ViewController 从 UIStoryboard 实例化时,isMemberOfClass 返回 no

    我有一个 OCUnit 测试类 PatientTestViewControllerTests 下面是界面 interface PatientTestViewControllerTests SenTestCase property nonat

随机推荐