I am working on a personal project where I will be connecting the user to a VPN. I have followed two blog posts on doing this which are https://medium.com/better-programming/how-to-build-an-openvpn-client-on-ios-c8f927c11e80 & https://kean.blog/post/vpn-configuration-manager. I was able to configure the VPN settings but still, it does not connect. Here are the images for my project Signing & Capabilities one for the App and the other is for the network extension.
my ViewController looks like this. There is a folder in the user app document directory that is named user which gets downloaded from the internet and these are the files that are in the folder. I am not sure if I am supposed to use it other than the user.ovpn file
import UIKit
import Zip
import Alamofire
import NetworkExtension
class ViewController: UIViewController {
var selectedServer: Server?
var providerManager: NETunnelProviderManager!
override func viewDidLoad() {
super.viewDidLoad()
self.loadProviderManager {
self.configureVPN(serverAddress: "openvpn://\(self.selectedServer!.server.address):\(self.selectedServer!.server.port)", username: "\(self.username)", password: "\(self.password)")
}
}
func loadProviderManager(completion:#escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(serverAddress: String, username: String, password: String) {
guard let configData = self.readFile(path: "user/user.ovpn") else { return }
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.example.Networking.tunnel"
tunnelProtocol.providerConfiguration = ["ovpn": configData, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "OpenVPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
func readFile(path: String) -> Data? {
let fileManager = FileManager.default
do {
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fileURL = documentDirectory.appendingPathComponent(path)
return try Data(contentsOf: fileURL, options: .uncached)
}
catch let error {
print(error.localizedDescription)
}
return nil
}
}
And my PacketTunnelProvider looks like this
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var vpnReachability = OpenVPNReachability()
var configuration: OpenVPNConfiguration!
var properties: OpenVPNProperties!
var UDPSession: NWUDPSession!
var TCPConnection: NWTCPConnection!
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else { return }
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
configuration.tunPersist = true
if !properties.autologin {
if let username: String = providerConfiguration["username"] as? String, let password: String = providerConfiguration["password"] as? String {
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
}
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
vpnAdapter.connect()
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
completionHandler()
}
override func wake() {
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
NSLog("Error: \(error.localizedDescription)")
NSLog("Connection Info: \(vpnAdapter.connectionInformation.debugDescription)")
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
NSLog("Log: \(logMessage)")
}
}
extension PacketTunnelProvider: OpenVPNAdapterPacketFlow {
func readPackets(completionHandler: #escaping ([Data], [NSNumber]) -> Void) {
packetFlow.readPackets(completionHandler: completionHandler)
}
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
return packetFlow.writePackets(packets, withProtocols: protocols)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
Related
I am trying to connect VPN using OpenVPNAdapter but the PacketTunnelProvider isn't called from the controller. What am i missing here?
Controller.swift
import NetworkExtension
var providerManager: NETunnelProviderManager!
var provider = PacketTunnelProvider()
override func viewDidLoad() {
super.viewDidLoad()
self.loadProviderManager {
self.configureVPN(response: self.arrResponse[0], serverAddress: self.arrResponse[0].iP, username: "vpn", password: "vpn")
}
}
func loadProviderManager(completion:#escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(response:Response,serverAddress: String, username: String, password: String) {
let data = Data(base64Encoded:response.openVPNConfigDataBase64, options: .ignoreUnknownCharacters)
print(data!)
let decodedString = String(data: data!, encoding: .utf8)!
print(decodedString)
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "***.*****.********.***********.********"
tunnelProtocol.providerConfiguration = ["ovpn": data!, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "SMVPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
if error == nil {
self.provider.startTunnel(options: nil) { error in //this called here not in network extension
if error != nil {
print(error!)
}else {
}
}
}else {
print(error!.localizedDescription)
}
})
}
})
}
}
}
Project Entitlement
PacketTunnelProvider.swift
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var vpnReachability = OpenVPNReachability()
var configuration: OpenVPNConfiguration!
var properties: OpenVPNConfigurationEvaluation!
var UDPSession: NWUDPSession!
var TCPConnection: NWTCPConnection!
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
// Add code here to start the process of connecting the tunnel.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else { return }
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
configuration.tunPersist = true
if !properties.autologin {
if let username: String = providerConfiguration["username"] as? String, let password: String = providerConfiguration["password"] as? String {
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
}
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
vpnAdapter.connect(using: self)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message.
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (Error?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow as? Error : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
NSLog("Error: \(error.localizedDescription)")
NSLog("Connection Info: \(vpnAdapter.connectionInformation.debugDescription)")
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
NSLog("Log: \(logMessage)")
}
}
extension PacketTunnelProvider: OpenVPNAdapterPacketFlow {
func readPackets(completionHandler: #escaping ([Data], [NSNumber]) -> Void) {
packetFlow.readPackets(completionHandler: completionHandler)
}
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
return packetFlow.writePackets(packets, withProtocols: protocols)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
Extension Entitlement
self.provider.startTunnel(options: nil) { error in // This is called here, not in network extension
This method is called from controller but didn't get called in network extension.
I posted all my code, so if I missed something, then please let me know. Any help is appreciated.
I found this question but I haven't figured it out yet.
PacketTunnelProvider network extension not called Swift 3
You haven't mentioned anything about how you are testing network extension. You should attach a Network extension process before running your project for debugging. Then only network extension methods will trigger for debug.
Finally i found my mistake it's i tried to called network extension but it will never called. so i called NETunnelProviderManager directly and it will work.
self.providerManager.loadFromPreferences(completionHandler: { (error) in
if error == nil {
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
}else {
print(error!.localizedDescription)
}
})
I have a WKWebView that is used to present the login screen of my OAuth Identity provider.
import UIKit
import WebKit
protocol OAuth2WKWebViewDelegate: class {
func didReceiveAuthorizationCode(_ code: String) -> Void
func didRevokeSession() -> Void
}
class OAuth2WKWebViewController: UIViewController {
let targetUrl: URLComponents
let webView = WKWebView()
weak var delegate: OAuth2WKWebViewDelegate?
init(targetUrl: URLComponents) {
self.targetUrl = targetUrl
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
loadUrl()
}
}
extension OAuth2WKWebViewController: WKNavigationDelegate {
func loadUrl() {
guard let url = targetUrl.url else { return }
view = webView
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
webView.navigationDelegate = self
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
if url.scheme == "appdev", url.absoluteString.range(of: "code") != nil {
let urlParts = url.absoluteString.components(separatedBy: "?")
let code = urlParts[1].components(separatedBy: "code=")[1]
delegate?.didReceiveAuthorizationCode(code)
}
if url.absoluteString == "appdev://oauth-callback-after-sign-out" {
delegate?.didRevokeSession()
}
}
decisionHandler(.allow)
}
}
I also have an IdentityService I use to present this view and respond to it's success / error.
protocol IdentityServiceProtocol {
var hasValidToken: Bool { get }
func initAuthCodeFlow() -> Void
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
func storeOAuthTokens(accessToken: String, refreshToken: String, completion: #escaping () -> Void) -> Void
func renderAuthView() -> Void
}
class IdentityService: IdentityServiceProtocol {
fileprivate var apiClient: APIClient
fileprivate var keyChainService: KeyChainService
init(apiClient: APIClient = APIClient(), keyChainService: KeyChainService = KeyChainService()) {
self.apiClient = apiClient
self.keyChainService = keyChainService
}
var hasValidToken: Bool {
return keyChainService.fetchSingleObject(withKey: "AccessToken") != nil
}
func initAuthCodeFlow() -> Void {
let queryItems = ["response_type": "code", "client_id": clientId, "redirect_uri": redirectUri, "state": state, "scope": scope]
renderOAuthWebView(forService: .auth(company: "benefex"), queryitems: queryItems)
}
func initRevokeSession() -> Void {
guard let refreshToken = keyChainService.fetchSingleObject(withKey: "RefreshToken") else { return }
let queryItems = ["refresh_token": refreshToken]
renderOAuthWebView(forService: .revokeSession(company: "benefex"), queryitems: queryItems)
}
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
guard let targetUrl = constructURLComponents(endPoint: service, queryItems: queryitems) else { return }
let webView = OAuth2WKWebViewController(targetUrl: targetUrl)
webView.delegate = self
UIApplication.shared.windows.first?.rootViewController = webView
}
func storeOAuthTokens(accessToken: String, refreshToken: String, completion: #escaping ()-> Void) -> Void {
let success = keyChainService.storeManyObjects(["AccessToken": accessToken, "RefreshToken": refreshToken])
guard success == true else { return }
completion()
}
func renderAuthView() -> Void {
UIApplication.shared.windows.first?.rootViewController = UINavigationController.init(rootViewController: AuthenticatedViewController())
}
}
extension IdentityService: OAuth2WKWebViewDelegate {
func didReceiveAuthorizationCode(_ code: String) {
apiClient.call(endpoint: IdentityEndpoint.accessToken(company: "benefex", code: code)) { [weak self] (response: OAuthTokenResponse) in
switch response {
case .success(let payload):
guard let accessToken = payload.accessToken, let refreshToken = payload.refreshToken else { return }
self?.storeOAuthTokens(accessToken: accessToken, refreshToken: refreshToken) { self?.renderAuthView() }
case .error:
// login failed for some reason
print("could not complete request for access token")
}
}
}
func didRevokeSession() {
print("This was called")
}
}
extension IdentityService {
fileprivate var state: String {
return generateState(withLength: 20)
}
fileprivate func constructURLComponents(endPoint: IdentityEndpoint, queryItems: [String: String]) -> URLComponents? {
var url = URLComponents(url: endPoint.baseUrl, resolvingAgainstBaseURL: false)
url?.path = endPoint.path
url?.queryItems = queryItems.map { URLQueryItem(name: $0.key, value: $0.value) }
return url
}
fileprivate func generateState(withLength len: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let length = UInt32(letters.count)
var randomString = ""
for _ in 0..<len {
let rand = arc4random_uniform(length)
let idx = letters.index(letters.startIndex, offsetBy: Int(rand))
let letter = letters[idx]
randomString += String(letter)
}
return randomString
}
}
extension IdentityService {
var clientId: String {
let envVar = ProcessInfo.processInfo.environment
guard let value = envVar["APP_CLIENT_ID"] else { fatalError("Missing APP_CLIENT_ID enviroment variable") }
return value
}
var redirectUri: String {
let envVar = ProcessInfo.processInfo.environment
guard let value = envVar["APP_REDIRECT_URI"] else { fatalError("Missing APP_REDIRECT_URI enviroment variable") }
return value
}
var scope: String {
let envVar = ProcessInfo.processInfo.environment
guard let value = envVar["APP_SCOPES"] else { fatalError("Missing APP_SCOPES enviroment variable") }
return value
}
}
The delegate?.didReceiveAuthorizationCode(code) is working.
However when delegate?.didRevokeSession() is called from the WebView, the identity service does not respond.
I added some console logs and can see my IdentityService is being de - init when I invoke the logout method.
I believe this is causing it to do nothing when the delegate method fires.
How can I ensure the delegate method is called still?
This came up when I was searching for answers to my issue - if you are using the 10.2 or 10.2.1 compiler - an issue was occurring for us when compiling in Release (instead of Debug) - where the delegate functions are not being called
The fix for us was to include #objc before all delegate function calls, IE
#objc func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void)
If you are in the same scenario - this should help
I am getting json data and parsing it, but for some reason when I change the address to different json and recall the function, (Im changing one eliment in album search), I get the same data in the json. I am thinking it is cached.
Here is my Address:
var SEARCH_RESULTS_ADDRESS = "\(BASE_URL)\(choiceSearch!).search&\(choiceSearch!)=\(albumSearch)&api_key=\(API_KEY)&format=json"
Here is my AddressDataService function.
func getAlbumData(completion: #escaping (_ finished: Bool) -> ()) {
guard let url = URL(string: SEARCH_RESULTS_ADDRESS) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let albumDataFull = try decoder.decode(Root.self, from: data)
albumInfo = []
for business in albumDataFull.results.albummatches.album {
let artist = business.artist!
let name = business.name!
let imgUrl = business.image
let albumInd = ["name":name, "artist":artist, "url":url, "imgUrl":imgUrl] as [String : Any]
albumInfo.append(albumInd)
print("Tony: \(albumInfo.count)")
}
completion(true)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}
}.resume()
}
Here is my relead data and address change.
func getData(completion: #escaping (_ finished: Bool) -> ()) {
AlbumDataService().getAlbumData(completion: { (complete) in
completion(true)
})
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
albumSearch = searchBar.text
albumInfo = []
getData(completion: { (complete) in
DispatchQueue.main.async {
SEARCH_RESULTS_ADDRESS = "\(BASE_URL)\(choiceSearch!).search&\(choiceSearch!)=\(albumSearch!)&api_key=\(API_KEY)&format=json"
print(SEARCH_RESULTS_ADDRESS)
self.tableView.reloadData()
print("Tony Finishedediting \(albumSearch!)")
}
})
}
You need to set SEARCH_RESULTS_ADDRESS before it is used.
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
albumSearch = searchBar.text
albumInfo = []
//###
SEARCH_RESULTS_ADDRESS = "\(BASE_URL)\(choiceSearch!).search&\(choiceSearch!)=\(albumSearch!)&api_key=\(API_KEY)&format=json"
print(SEARCH_RESULTS_ADDRESS)
getData(completion: { (complete) in
DispatchQueue.main.async {
self.tableView.reloadData()
print("Tony Finishedediting \(albumSearch!)")
}
})
}
(Better make it a parameter than declaring a constant-like property.)
(ADDITION)
how I would make the address a param?
Assuming you can modify the methods of AddressDataService:
func getAlbumData(_ urlString: String, completion: #escaping (_ finished: Bool) -> ()) {
print(urlString)
guard let url = URL(string: urlString) else {
print("ursString is invalid")
return
}
//...
}
And you can use it as:
func getData(_ urlString: String, completion: #escaping (_ finished: Bool) -> ()) {
AlbumDataService().getAlbumData(urlString) { complete in
completion(true)
}
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
albumSearch = searchBar.text
albumInfo = []
let searchResultsAddress = "\(BASE_URL)\(choiceSearch!).search&\(choiceSearch!)=\(albumSearch!)&api_key=\(API_KEY)&format=json"
getData(searchResultsAddress) { complete in
DispatchQueue.main.async {
self.tableView.reloadData()
print("Tony Finishedediting \(albumSearch!)")
}
}
}
in watchOS, i am just trying to get HR value in real-time while a workoutSession is running.
func startHeartRateQuery(updateHandler: #escaping ([HKQuantitySample]?) -> Void) {
guard let quantityType = HKObjectType.quantityType(forIdentifier: .heartRate) else {
return
}
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
guard let newAnchor = newAnchor else {return}
self.anchor = newAnchor
updateHandler(sampleObjects as? [HKQuantitySample])
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.anchor = newAnchor!
updateHandler(samples as? [HKQuantitySample])
}
healthStore.execute(heartRateQuery)
activeDataQueries.append(heartRateQuery)
}
And This is how i do start workoutSession.
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
guard let d = delegate else {
return
}
if let u = userInfo["status"] as? String {
d.receivedWorkoutRunningStatus(u)
}
if let c = userInfo["clock"] as? String {
d.receiveWorkoutRestPeriodClock(c)
}
}
You can find receivedWorkoutRunningStatus function on InterfaceController.
This is InterfaceController.swift which is first screen on Watch App.
class InterfaceController: WKInterfaceController, HKWorkoutSessionDelegate {
#IBOutlet var lblHeartRate: WKInterfaceLabel!
#IBOutlet var lblSplitIntervalNumber: WKInterfaceLabel!
#IBOutlet var lblRestPeriodClock: WKInterfaceLabel!
private let healthStoreManager = WatchHealthKitManager()
private let parentConnector = ParentConnector()
private var workoutSession: HKWorkoutSession!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Create a workout session with the workout configuration
do {
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.locationType = .indoor
workoutConfiguration.activityType = .rowing
workoutSession = try HKWorkoutSession(configuration: workoutConfiguration)
} catch {
fatalError(error.localizedDescription)
}
//// initial setup
parentConnector.activate()
parentConnector.delegate = self
workoutSession.delegate = self
}
// MARK: - Data Accumulation
private func startAccumulatingData() {
healthStoreManager.startHeartRateQuery() { quantitySamples in
DispatchQueue.main.async {
guard !self.isPaused() else {
return
}
guard let heartRateSamples = quantitySamples else {
return
}
let hrUnit = HKUnit(from: "count/min")
guard let sample = heartRateSamples.first else {
return
}
let value = sample.quantity.doubleValue(for: hrUnit)
self.updateHeartRate(value: value)
self.parentConnector.transfer(value: value)
}
}
}
func workoutSession(_ workoutSession: HKWorkoutSession,
didChangeTo toState: HKWorkoutSessionState,
from fromState: HKWorkoutSessionState,
date: Date) {
switch (toState) {
case .running:
startAccumulatingData()
case .ended:
stopAccumulatingData()
default:
print("Error")
}
}
func receivedWorkoutRunningStatus(_ status: String) {
if (status == "Start") {
healthStoreManager.start(workoutSession)
} else if (status == "Finish") {
healthStoreManager.end(workoutSession)
lblHeartRate.setText("No Active Workout")
}
DispatchQueue.main.async {
self.lblSplitIntervalNumber.setText(status)
}
}
On the iPhone App, I send a "Start" string using transferUserInfo function to trigger the workoutSession beginning. This is not working properly, it only sometimes works, it is very inconsistent.
I would appreciate if you have any advice or alternative approaches.
Thank you in advance.
I have been using UIImageView+AFNetworking.swift in my application to download images, but after updating to Xcode 7, it gives me an error.
Here is the full code:
import UIKit
#objc public protocol AFImageCacheProtocol:class{
func cachedImageForRequest(request:NSURLRequest) -> UIImage?
func cacheImage(image:UIImage, forRequest request:NSURLRequest);
}
extension UIImageView {
private struct AssociatedKeys {
static var SharedImageCache = "SharedImageCache"
static var RequestImageOperation = "RequestImageOperation"
static var URLRequestImage = "UrlRequestImage"
}
public class func setSharedImageCache(cache:AFImageCacheProtocol?) {
objc_setAssociatedObject(self, &AssociatedKeys.SharedImageCache, cache, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN))
}
public class func sharedImageCache() -> AFImageCacheProtocol {
struct Static {
static var token : dispatch_once_t = 0
static var defaultImageCache:AFImageCache?
}
dispatch_once(&Static.token, { () -> Void in
Static.defaultImageCache = AFImageCache()
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationDidReceiveMemoryWarningNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (NSNotification) -> Void in
Static.defaultImageCache!.removeAllObjects()
}
})
return objc_getAssociatedObject(self, &AssociatedKeys.SharedImageCache) as? AFImageCacheProtocol ?? Static.defaultImageCache!
}
private class func af_sharedImageRequestOperationQueue() -> NSOperationQueue {
struct Static {
static var token:dispatch_once_t = 0
static var queue:NSOperationQueue?
}
dispatch_once(&Static.token, { () -> Void in
Static.queue = NSOperationQueue()
Static.queue!.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount
})
return Static.queue!
}
private var af_requestImageOperation:(operation:NSOperation?, request: NSURLRequest?) {
get {
let operation:NSOperation? = objc_getAssociatedObject(self, &AssociatedKeys.RequestImageOperation) as? NSOperation
let request:NSURLRequest? = objc_getAssociatedObject(self, &AssociatedKeys.URLRequestImage) as? NSURLRequest
return (operation, request)
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.RequestImageOperation, newValue.operation, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
objc_setAssociatedObject(self, &AssociatedKeys.URLRequestImage, newValue.request, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
public func setImageWithUrl(url:NSURL, placeHolderImage:UIImage? = nil) {
let request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.addValue("image/*", forHTTPHeaderField: "Accept")
self.setImageWithUrlRequest(request, placeHolderImage: placeHolderImage, success: nil, failure: nil)
}
public func setImageWithUrlRequest(request:NSURLRequest, placeHolderImage:UIImage? = nil,
success:((request:NSURLRequest?, response:NSURLResponse?, image:UIImage, fromCache:Bool) -> Void)?,
failure:((request:NSURLRequest?, response:NSURLResponse?, error:NSError) -> Void)?)
{
self.cancelImageRequestOperation()
if let cachedImage = UIImageView.sharedImageCache().cachedImageForRequest(request) {
if success != nil {
success!(request: nil, response:nil, image: cachedImage, fromCache:true)
}
else {
self.image = cachedImage
}
return
}
if placeHolderImage != nil {
self.image = placeHolderImage
}
self.af_requestImageOperation = (NSBlockOperation(block: { () -> Void in
var response:NSURLResponse?
var error:NSError?
let data: NSData?
do {
data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)
} catch let error1 as NSError {
error = error1
data = nil
} catch {
fatalError()
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if request.URL!.isEqual(self.af_requestImageOperation.request?.URL) {
let image:UIImage? = (data != nil ? UIImage(data: data!) : nil)
if image != nil {
if success != nil {
success!(request: request, response: response, image: image!, fromCache:false)
}
else {
self.image = image!
}
UIImageView.sharedImageCache().cacheImage(image!, forRequest: request)
}
else {
if failure != nil {
failure!(request: request, response:response, error: error!)
}
}
self.af_requestImageOperation = (nil, nil)
}
})
}), request)
UIImageView.af_sharedImageRequestOperationQueue().addOperation(self.af_requestImageOperation.operation!)
}
private func cancelImageRequestOperation() {
self.af_requestImageOperation.operation?.cancel()
self.af_requestImageOperation = (nil, nil)
}
}
func AFImageCacheKeyFromURLRequest(request:NSURLRequest) -> String {
return request.URL!.absoluteString
}
class AFImageCache: NSCache, AFImageCacheProtocol {
func cachedImageForRequest(request: NSURLRequest) -> UIImage? {
switch request.cachePolicy {
case NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData,
NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData:
return nil
default:
break
}
return self.objectForKey(AFImageCacheKeyFromURLRequest(request)) as? UIImage
}
func cacheImage(image: UIImage, forRequest request: NSURLRequest) {
self.setObject(image, forKey: AFImageCacheKeyFromURLRequest(request))
}
}
Error image:
Error 1:
objc_setAssociatedObject(self, &AssociatedKeys.SharedImageCache, cache, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN))
Error 2:
objc_setAssociatedObject(self, &AssociatedKeys.RequestImageOperation, newValue.operation, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
Error 3:
objc_setAssociatedObject(self, &AssociatedKeys.URLRequestImage, newValue.request, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
Does anyone has any idea how I can fix this?
The reason for this is exactly as the error states; that UInt cannot be created with an argument of type objc_AssociationPolicy.
objc_setAssociatedObject expects the policy parameter to be of type objc_AssociationPolicy, you don't need to convert it to UInt at all.
objc_setAssociatedObject(self, &AssociatedKeys.SharedImageCache, cache, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)