DJI drone not connecting (sdkManagerProductDidChange delegate method not called) - ios

I can't seem to get my own app to connect to a particular drone. I have downloaded the bridger app and am using it to debug. I copied the "DJIBaseViewController" from the sample app and have made my own view controller a delegate of it. After adding alot of breakpoints to the code I have found that the major difference between my app and the sample app is that the delegate method "sdkManagerProductDidChange."
// DJIBaseViewController.swift
import UIKit
import DJISDK
protocol DJIProductObjectProtocol {
func fetchAircraft() -> DJIAircraft?
func fetchCamera() -> DJICamera?
func fetchGimbal() -> DJIGimbal?
func fetchFlightController() -> DJIFlightController?
func fetchRemoteController() -> DJIRemoteController?
func fetchBattery() -> DJIBattery?
func fetchAirLink() -> DJIAirLink?
func fetchHandheldController() -> DJIHandheldController?
}
class ConnectedProductManager: DJIProductObjectProtocol {
static let sharedInstance = ConnectedProductManager()
var connectedProduct:DJIBaseProduct? = nil
func fetchAircraft() -> DJIAircraft? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft)
}
return nil
}
func fetchCamera() -> DJICamera? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft).camera
}
else if (self.connectedProduct is DJIHandheld) {
return (self.connectedProduct as! DJIHandheld).camera
}
return nil
}
func fetchGimbal() -> DJIGimbal? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft).gimbal
}
else if (self.connectedProduct is DJIHandheld) {
return (self.connectedProduct as! DJIHandheld).gimbal
}
return nil
}
func fetchFlightController() -> DJIFlightController? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft).flightController
}
return nil
}
func fetchRemoteController() -> DJIRemoteController? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft).remoteController
}
return nil
}
func fetchBattery() -> DJIBattery? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft).battery
}
else if (self.connectedProduct is DJIHandheld) {
return (self.connectedProduct as! DJIHandheld).battery
}
return nil
}
func fetchAirLink() -> DJIAirLink? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIAircraft) {
return (self.connectedProduct as! DJIAircraft).airLink
}
else if (self.connectedProduct is DJIHandheld) {
return (self.connectedProduct as! DJIHandheld).airLink
}
return nil
}
func fetchHandheldController() -> DJIHandheldController? {
if (self.connectedProduct == nil) {
return nil
}
if (self.connectedProduct is DJIHandheld) {
return (self.connectedProduct as! DJIHandheld).handheldController
}
return nil
}
func setDelegate(delegate:DJIBaseProductDelegate?) {
self.connectedProduct?.delegate = delegate
}
}
class DJIBaseViewController: UIViewController, DJIBaseProductDelegate, DJIProductObjectProtocol {
//var connectedProduct:DJIBaseProduct?=nil
var moduleTitle:String?=nil
override func viewDidLoad() {
super.viewDidLoad()
if (moduleTitle != nil) {
self.title = moduleTitle
}
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (ConnectedProductManager.sharedInstance.connectedProduct != nil) {
ConnectedProductManager.sharedInstance.setDelegate(self)
}
}
override func viewWillDisappear(
animated: Bool) {
super.viewWillDisappear(animated)
if (ConnectedProductManager.sharedInstance.connectedProduct != nil &&
ConnectedProductManager.sharedInstance.connectedProduct?.delegate === self) {
ConnectedProductManager.sharedInstance.setDelegate(nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func product(product: DJIBaseProduct, connectivityChanged isConnected: Bool) {
if isConnected {
NSLog("\(product.model) connected. ")
ConnectedProductManager.sharedInstance.connectedProduct = product
ConnectedProductManager.sharedInstance.setDelegate(self)
}
else {
NSLog("Product disconnected. ")
ConnectedProductManager.sharedInstance.connectedProduct = nil
}
}
func componentWithKey(withKey key: String, changedFrom oldComponent: DJIBaseComponent?, to newComponent: DJIBaseComponent?) {
// (newComponent as? DJICamera)?.delegate = self
if ((newComponent is DJICamera) == true && (self is DJICameraDelegate) == true) {
(newComponent as! DJICamera).delegate = self as? DJICameraDelegate
}
if ((newComponent is DJICamera) == true && (self is DJIPlaybackDelegate) == true) {
(newComponent as! DJICamera).playbackManager?.delegate = self as? DJIPlaybackDelegate
}
if ((newComponent is DJIFlightController) == true && (self is DJIFlightControllerDelegate) == true) {
(newComponent as! DJIFlightController).delegate = self as? DJIFlightControllerDelegate
}
if ((newComponent is DJIBattery) == true && (self is DJIBatteryDelegate) == true) {
(newComponent as! DJIBattery).delegate = self as? DJIBatteryDelegate
}
if ((newComponent is DJIGimbal) == true && (self is DJIGimbalDelegate) == true) {
(newComponent as! DJIGimbal).delegate = self as? DJIGimbalDelegate
}
if ((newComponent is DJIRemoteController) == true && (self is DJIRemoteControllerDelegate) == true) {
(newComponent as! DJIRemoteController).delegate = self as? DJIRemoteControllerDelegate
}
}
func showAlertResult(info:String) {
// create the alert
var message:String? = info
if info.hasSuffix(":nil") {
message = info.stringByReplacingOccurrencesOfString(":nil", withString: " success")
}
let alert = UIAlertController(title: "Message", message: "\(message ?? "")", preferredStyle: .Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
}
func fetchAircraft() -> DJIAircraft?{
return ConnectedProductManager.sharedInstance.fetchAircraft()
}
func fetchCamera() -> DJICamera? {
return ConnectedProductManager.sharedInstance.fetchCamera()
}
func fetchGimbal() -> DJIGimbal? {
return ConnectedProductManager.sharedInstance.fetchGimbal()
}
func fetchFlightController() -> DJIFlightController? {
return ConnectedProductManager.sharedInstance.fetchFlightController()
}
func fetchRemoteController() -> DJIRemoteController? {
return ConnectedProductManager.sharedInstance.fetchRemoteController()
}
func fetchBattery() -> DJIBattery? {
return ConnectedProductManager.sharedInstance.fetchBattery()
}
func fetchAirLink() -> DJIAirLink? {
return ConnectedProductManager.sharedInstance.fetchAirLink()
}
func fetchHandheldController() -> DJIHandheldController?{
return ConnectedProductManager.sharedInstance.fetchHandheldController()
}
}
The first view that loads after the splashscreen loads is.
// MenuViewController.swift
import UIKit
import DJISDK
let enterDebugMode=true
class MenuViewController: DJIBaseViewController {
#IBOutlet weak var aircraft: UILabel!
#IBOutlet weak var productID: UILabel!
// Do any additional setup after loading the view.
#IBOutlet weak var appConectivity: UILabel!
var connectedProduct:DJIBaseProduct?=nil
var componentDictionary = Dictionary<String, Array<DJIBaseComponent>>()
let APP_KEY = "*******"//Please enter App Key Here
override func viewDidLoad() {
super.viewDidLoad()
let air = self.fetchAircraft()
if air == nil{
aircraft.text?="no aircraft connected"
}
print(air?.model)
initUI();
guard !APP_KEY.isEmpty else {
showAlert("Please enter your app key.")
return
}
DJISDKManager.registerApp(APP_KEY, withDelegate: self)
if DJISDKManager.product() == nil{
productID.text?="Drone Not Connected"
}
else{
productID.text? = "Drone Connected"
}
}
func initUI() {
self.title = "DJI iOS SDK Sample"
//sdkVersionLabel.text = "DJI SDK Version: \(DJISDKManager.getSDKVersion())"
//openComponents.isEnabled = false;
//bluetoothConnectorButton.isEnabled = true;
//productModel.isHidden = true
//productFirmwarePackageVersion.isHidden = true
//debugModeLabel.isHidden = !enterDebugMode
}
func showAlert(msg: String?) {
// create the alert
let alert = UIAlertController(title: "", message: msg, preferredStyle: .Alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
extension MenuViewController: DJISDKManagerDelegate{
func sdkManagerDidRegisterAppWithError(error: NSError?) {
guard error == nil else {
self.showAlertResult("Error:\(error!.localizedDescription)")
appConectivity.text?="app isn't registering properly"
return
}
//Debug("Registered!")
if enterDebugMode {
DJISDKManager.enterDebugModeWithDebugId("10.202.38.238")
print("WTF")
}else{
//DJISDKManager.enterDebugModeWithDebugId("10.202.38.238")
DJISDKManager.startConnectionToProduct()
}
}
func sdkManagerProductDidChange(From oldProduct: DJIBaseProduct?, To newProduct: DJIBaseProduct?) {
print("entered changed product")
if oldProduct==nil{
print("old product is nill")
}
if newProduct==nil{
print("new product is nill")
}
guard let newProduct = newProduct else
{
appConectivity.text? = "Status: No Product Connected"
ConnectedProductManager.sharedInstance.connectedProduct = nil
//logDebug("Product Disconnected")
return
}
//Updates the product's model
productID.text = "Model: \((newProduct.model)!)"
productID.hidden = false
if let oldProduct = oldProduct {
print("Product changed from: \(oldProduct.model) to \((newProduct.model)!)")
}
//Updates the product's firmware version - COMING SOON
//Updates the product's connection status
//appConectivity.text = "Status: Product Connected"
ConnectedProductManager.sharedInstance.connectedProduct = newProduct
productID.text?="product connected"
//openComponents.isEnabled = true;
//openComponents.alpha = 1.0;
//logDebug("Product Connected")
}
override func product(product: DJIBaseProduct, connectivityChanged isConnected: Bool) {
if isConnected {
print("Status: Product Connected")
//appConectivity.text?="Drone Recognized"
} else {
print("Status: No Product Connected")
//appConectivity.text="Atleast Its trying"
}
}
}
The sdkManager is registering properly with the given app key and bundler identifier. I have also added "Supported external accessory protocols" to my info.plist file with three elements com.dji.video, com.dji.protocol, and com.dji.common.
Been stuck here for quite some time and it's been damn frustrating. Hope someone call help.
Thanks in advance.

I figured it out. The issue here is that the sample app has named its delegate
func sdkManagerProductDidChange(from oldProduct: DJIBaseProduct?, to newProduct: DJIBaseProduct?)
whereas for whatever reason in my sample app the DJISDK knows my delegates function as
func sdkManagerProductDidChangeFrom(oldProduct: DJIBaseProduct?, to newProduct: DJIBaseProduct?)
a bit annoying that that made such a big difference but that's what I get for copying code I guess. Hope this is helpful to someone else down the road...
Cheers
P.S. they may change it again the way I found it is when I typed func into my extension section Xcode spit out a list of functions I could use and the sdkManagerDidChange was one of them with different input variables.
edit: if anyone can explain why it worked on the sample app and not mine that would be sweet too.

Related

Only instance methods can be declared #IBAction error?

I am facing this error on build for the function shown in the code
Only instance methods can be declared #IBAction
this error is coming up only after I introduced google sign in method for similar functionality , earlier it not an error
#IBAction func SignInButtonAction(_ sender: Any) {
guard let email = emailField.text else { return }
guard let pass = passwordField.text else { return }
Auth.auth().signIn(withEmail: email, password: pass) { user, error in
if error == nil && user != nil {
let setupcheckref = Firestore.firestore().collection("users").document(Auth.auth().currentUser!.uid)
setupcheckref.getDocument{(document, error) in
if let document = document, document.exists{
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
self.checksetup = document.get("setupComplete") as! Bool
if self.checksetup == true {
if Auth.auth().currentUser!.isEmailVerified {
self.performSegue(withIdentifier: "toLoginFeed", sender: self)
}
else{
print("please verify your email")
try! Auth.auth().signOut()
}
}
else{
self.view.makeToast("Please Setup Your Account!", duration: 2.5)
self.performSegue(withIdentifier: "fromlogintosetup", sender: self)
SVProgressHUD.dismiss()
} }
}
// self.dismiss(animated: false, completion: nil)
} else {
print("Error logging in: \(error!.localizedDescription)")
// self.resetForm()
// SVProgressHUD.dismiss()
}
}
}
That means you can create #IBActions only as instance methods of a class.
You might be creating it of a class.
class VC: UIViewController {
#IBAction func SignInButtonAction(_ sender: Any) {
//your code...
}
}

Sinch Video calling sound is coming from front speaker

I have implemented sinch video calling in ios swift project i have followed all process given in sinch implementation document https://www.sinch.com/docs/video/ios/#calling. And i am successfully able to implement but i am getting on issue my video sound in coming from front speaker. how can i solve this problem?? Below my code:
var client: SINClient?
var sinCall : SINCall?
Configuring sinch
//MARK: Configuring Sinch Delegate
func configuringSinch(){
//Configuring Client Key
client = Sinch.client(withApplicationKey: Constants.SINCH_APP_KEY, applicationSecret: Constants.SINCH_PRIVATE_KEY, environmentHost: Constants.SANDBOX_ENVIRONMENT, userId: Utility().getUserId())
client?.call().delegate = self
client?.setSupportCalling(true)
client?.enableManagedPushNotifications()
client?.start()
client?.startListeningOnActiveConnection()
let vcCont = client?.videoController()
self.vwLocalView.addSubview((vcCont?.localView())!)
self.sinCall?.delegate = self
}
//MARK: Sinch Video Call Delegate
func clientDidStart(_ client: SINClient!) {
print("Client Did Start")
}
func clientDidFail(_ client: SINClient!, error: Error!) {
print("Client failed : \(error)")
player?.stop()
}
func clientDidStop(_ client: SINClient!) {
print("Client Did Stop")
player?.stop()
}
//MARK: Video Call Did Recieve
func client(_ client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
print("Did Recieve Incoming Call")
playRingtoneSound() // Playing Audio
call.delegate = self;
self.sinCall = call
}
//MARK: Call Did Add Video Track
func callDidAddVideoTrack(_ call: SINCall!) {
let videoCont = client?.videoController()
vwRemoteView.addSubview((videoCont?.remoteView())!)
}
func callDidEnd(_ call: SINCall!) {
sinCall?.hangup()
}
This is how you can manage SINAudioController to manage audio output.
func audioController() -> SINAudioController {
return (client?.audioController())!
}
//MARK: Video Call Did Recieve
func client(_ client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
audioController().enableSpeaker()
playRingtoneSound() // Playing Audio
call.delegate = self;
self.sinCall = call
}
// In SINCallDelegate
func callDidEstablish(_ call: SINCall!) {
//to disableSpeaker
audioController().disableSpeaker()
}
try this to manage AudioOutput Session manually
// MARK: AudioOutput Session
// to enable front speaker manually
func setSessionPlayerOn()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.none)
} catch _ {
}
}
// to enable speaker manually
func setSessionPlayerSpeaker()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
} catch _ {
}
}
// to turnoff AudioOutput Session manually
func setSessionPlayerOff()
{
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch _ {
}
}
Use this function
func callDidEstablish(_ call: SINCall!) {
let audio = APPDELEGATE.client?.audioController()
audio?.disableSpeaker()
// self.startCallDurationTimerWithSelector()
appDelegate.client?.audioController().stopPlayingSoundFile()
}
this is work for me.
class MainVC: UIViewController,SINCallDelegate {
// var client: SINClient?
private let videoController = SinchManager.sharedInstance.client!.videoController()
private let audioController = SinchManager.sharedInstance.client!.audioController()
private let callClient: SINCallClient
private var call: SINCall!
let username: String
#IBOutlet weak var otherView: UIView!
// private var mainView: SinchView { return view as! SinchView }
#IBAction func call_btn(_ sender: UIButton) {
answer()
}
#IBAction func end_btn(_ sender: UIButton) {
decline()
}
override func loadView() {
// view = SinchView()
view = otherView
}
init(username: String) {
self.username = username
self.callClient = SinchManager.sharedInstance.client!.call()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
print("init(coder:) has not been implemented " + String(describing: aDecoder))
fatalError("init(coder:) has not been implemented " + String(describing: aDecoder))
}
override func viewDidLoad() {
super.viewDidLoad()
call.delegate = self
//self.mainView.videoView.addSubview(self.videoController.localView())
otherView.addSubview((self.videoController?.localView())!)
self.videoController?.localView().contentMode = .scaleToFill
if self.call.direction == SINCallDirection.incoming {
self.audioController?.startPlayingSoundFile(self.pathForSound(string: "incoming.wav") as String, loop: true)
}
if self.call.details.isVideoOffered {
print("video offered")
//self.mainView.videoView.addSubview(self.videoController.localView())
otherView.addSubview((self.videoController?.localView())!)
self.videoController?.localView().contentMode = .scaleToFill
}
otherView.addSubview((self.videoController?.localView())!)
// mainView.answerButton.addTarget(self, action: #selector(answer), forControlEvents: .TouchUpInside)
// mainView.declineButton.addTarget(self, action: #selector(decline), forControlEvents: .TouchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.audioController?.enableSpeaker()
}
func pathForSound(string: String) -> NSString {
let nsSt = Bundle.main.resourcePath! as NSString
return nsSt.appendingPathComponent(string) as NSString
}
func answer() {
call.answer()
}
func decline() {
call.hangup()
}
func callDidEstablish(call: SINCall!) {
print("callDidEstablish")
let audio = SinchManager.sharedInstance.client!.audioController()
audio?.disableSpeaker()
// self.startCallDurationTimerWithSelector()
SinchManager.sharedInstance.client!.audioController().stopPlayingSoundFile()
}
func callDidEnd(call: SINCall!) {
print("callDidEnd")
}
func callDidProgress(call: SINCall!) {
print("callDidProgress")
self.audioController?.startPlayingSoundFile(self.pathForSound(string: "ringback.wav") as String, loop: true)
}
func callDidAddVideoTrack(call: SINCall!) {
print("callDidAddVideoTrack")
otherView.addSubview((self.videoController?.localView())!)
}

NavigationTitle blink

If app is killed and user click on push notification app takes it directly to the respective screen(detail screen) but before pushing to the detail screen we push to the list screen.When user click on back button of detail screen navigation title of list screen comes empty for a sec then gets correctly.What could be the reason.
switch(model!.action[1].lowercased()){
case "profile":
NotificationUtils.sharedInstance.moveToDirectoryModuleWithLTDS() //Will shift to Directory module
NotificationUtils.sharedInstance.moveToLocationList(false)
NotificationUtils.sharedInstance.moveToLocation(Guid(locID),rightPanel: false, needAnimation: false) //Move to department detail screen with Info tab selected
hideLoader() //Will stop loading indicator
break
}
func moveToLocationList(_ animate: Bool = true) {
hideNavigationTitle() //This function is used to clear the back button title.
let dropVC = DepartmentViewController(nibName: "DepartmentViewController", bundle: nil)
dropVC.employeeGroupInfo = EmployeeGroupInfo.locationInfo
self.pushWithDelayToVC(dropVC, animation: animate)
}
func pushWithDelayToVC(_ viewController:UIViewController,animation:Bool = true)
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
AppDelegate.removeUserProfileSetupScreens()
self.removeComposer { () -> Void in
UIUtils.pushViewWhenHideBottom((appDelegate.tabBarController.selectedViewController as! UINavigationController).visibleViewController!, anotherVC: viewController, needAnimation: animation)
self.hideSidePanel()
}
}
func removeComposer(_ completion: #escaping (() -> Void))
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let selectedController = appDelegate.tabBarController.selectedViewController
{
if let selectNavController = selectedController as? UINavigationController
{
if let visibleController = selectNavController.visibleViewController
{
if (NSStringFromClass(type(of: visibleController)) == "MFMailComposeInternalViewController" || NSStringFromClass(type(of: visibleController)) == "CKSMSComposeController" || NSStringFromClass(type(of: visibleController)) == "PLUICameraViewController" || NSStringFromClass(type(of: visibleController)) == "UIAlertController")
{
visibleController.dismiss(animated: false, completion: { () -> Void in
completion()
return
})
}
else
{
completion()
}
}
else { completion() }
}
else { completion()}
}
else { completion() }
}
And code of viewWillAppear is :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if employeeGroupInfo == EmployeeGroupInfo.departmentInfo {
UIUtils.setTitleFontWhiteOnNavigationController(viewCtrl: self, titleStr: AppMessage.EDByDepartment)
} else if employeeGroupInfo == EmployeeGroupInfo.locationInfo {
UIUtils.setTitleFontWhiteOnNavigationController(viewCtrl: self, titleStr: AppMessage.EDByLocation)
} else if employeeGroupInfo == EmployeeGroupInfo.teamInfo {
UIUtils.setTitleFontWhiteOnNavigationController(viewCtrl: self, titleStr: AppMessage.EDMoreByTeamsMob)
}
self.tabBarController?.tabBar.isHidden = false
}
public class func setTitleFontWhiteOnNavigationController(viewCtrl: UIViewController, titleStr: String, needBorder: Bool = true,needTitleImage: Bool = false)
{
viewCtrl.extendedLayoutIncludesOpaqueBars = true
viewCtrl.navigationController?.navigationBar.isTranslucent = false
viewCtrl.navigationController?.navigationBar.barTintColor = UIColor.LTColor()
// Set navigation bar background color
viewCtrl.navigationController?.navigationBar.tintColor = UIColor.white
viewCtrl.navigationController?.navigationBar.titleTextAttributes = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 18.0),NSForegroundColorAttributeName: UIColor.white]
if needTitleImage {
setImageWithTitle(viewCtrl: viewCtrl,titleStr: titleStr)
}
else {
viewCtrl.title = titleStr
}
if needBorder
{
//Used to hide shadow line in navigation bar by Nikhil
viewCtrl.navigationController?.navigationBar.setBackgroundImage(nil, for: UIBarMetrics.default)
viewCtrl.navigationController?.navigationBar.shadowImage = nil
}
else
{
//Used to hide shadow line in navigation bar by Nikhil
viewCtrl.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
viewCtrl.navigationController?.navigationBar.shadowImage = UIImage()
}
}

ViewController loads but does not show

I'm using the Spotify iOS SDK. When a user logs into Spotify using the app, on call back loginVC transitions to musicPlayerVC. But, when a user logs into the app using a web view, once the web view dismisses and the loginVC is shown, the musicPlayerVC is loaded (print statements from viewDidLoad occur), but loginVC does not dismiss and musicPlayerVC does not show.
loginVC:
class loginVC: UIViewController, SPTStoreControllerDelegate, WebViewControllerDelegate {
#IBOutlet weak var statusLabel: UILabel!
var authViewController: UIViewController?
var firstLoad: Bool!
var Information: [String:String]?
override func viewDidLoad() {
super.viewDidLoad()
// NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
self.statusLabel.text = ""
self.firstLoad = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
let auth = SPTAuth.defaultInstance()
// Uncomment to turn off native/SSO/flip-flop login flow
//auth.allowNativeLogin = NO;
// Check if we have a token at all
if auth!.session == nil {
self.statusLabel.text = ""
return
}
// Check if it's still valid
if auth!.session.isValid() && self.firstLoad {
// It's still valid, show the player.
print("View did load, still valid, showing player")
self.showPlayer()
return
}
// Oh noes, the token has expired, if we have a token refresh service set up, we'll call tat one.
self.statusLabel.text = "Token expired."
if auth!.hasTokenRefreshService {
self.renewTokenAndShowPlayer()
return
}
// Else, just show login dialog
}
override var prefersStatusBarHidden: Bool {
return true
}
func getAuthViewController(withURL url: URL) -> UIViewController {
let webView = WebViewController(url: url)
webView.delegate = self
return UINavigationController(rootViewController: webView)
}
func sessionUpdatedNotification(_ notification: Notification) {
self.statusLabel.text = ""
let auth = SPTAuth.defaultInstance()
self.presentedViewController?.dismiss(animated: true, completion: { _ in })
if auth!.session != nil && auth!.session.isValid() {
self.statusLabel.text = ""
print("Session updated, showing player")
self.showPlayer()
}
else {
self.statusLabel.text = "Login failed."
print("*** Failed to log in")
}
}
func showPlayer() {
self.firstLoad = false
self.statusLabel.text = "Logged in."
self.Information?["SpotifyUsername"] = SPTAuth.defaultInstance().session.canonicalUsername
OperationQueue.main.addOperation {
[weak self] in
self?.performSegue(withIdentifier: "ShowPlayer", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowPlayer" {
if let destination = segue.destination as? PlayController {
destination.Information = self.Information
}
}
}
internal func productViewControllerDidFinish(_ viewController: SPTStoreViewController) {
self.statusLabel.text = "App Store Dismissed."
viewController.dismiss(animated: true, completion: { _ in })
}
func openLoginPage() {
self.statusLabel.text = "Logging in..."
let auth = SPTAuth.defaultInstance()
if SPTAuth.supportsApplicationAuthentication() {
self.open(url: auth!.spotifyAppAuthenticationURL())
} else {
self.authViewController = self.getAuthViewController(withURL: SPTAuth.defaultInstance().spotifyWebAuthenticationURL())
self.definesPresentationContext = true
self.present(self.authViewController!, animated: true, completion: { _ in })
}
}
func open(url: URL) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:],
completionHandler: {
(success) in
print("Open \(url): \(success)")
})
} else {
let success = UIApplication.shared.openURL(url)
print("Open \(url): \(success)")
}
}
func renewTokenAndShowPlayer() {
self.statusLabel.text = "Refreshing token..."
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
SPTAuth.defaultInstance().session = session
if error != nil {
self.statusLabel.text = "Refreshing token failed."
print("*** Error renewing session: \(error)")
return
}
self.showPlayer()
}
}
func webViewControllerDidFinish(_ controller: WebViewController) {
// User tapped the close button. Treat as auth error
}
}
webController :
import UIKit
import WebKit
#objc protocol WebViewControllerDelegate {
func webViewControllerDidFinish(_ controller: WebViewController)
/*! #abstract Invoked when the initial URL load is complete.
#param success YES if loading completed successfully, NO if loading failed.
#discussion This method is invoked when SFSafariViewController completes the loading of the URL that you pass
to its initializer. It is not invoked for any subsequent page loads in the same SFSafariViewController instance.
*/
#objc optional func webViewController(_ controller: WebViewController, didCompleteInitialLoad didLoadSuccessfully: Bool)
}
class WebViewController: UIViewController, UIWebViewDelegate {
var loadComplete: Bool = false
var initialURL: URL!
var webView: UIWebView!
var delegate: WebViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
print(initialURL)
let initialRequest = URLRequest(url: self.initialURL)
self.webView = UIWebView(frame: self.view.bounds)
self.webView.delegate = self
self.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(self.webView)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.done))
self.webView.loadRequest(initialRequest)
}
func done() {
self.delegate?.webViewControllerDidFinish(self)
self.presentingViewController?.dismiss(animated: true, completion: { _ in })
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if !self.loadComplete {
delegate?.webViewController?(self, didCompleteInitialLoad: true)
self.loadComplete = true
}
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
if !self.loadComplete {
delegate?.webViewController?(self, didCompleteInitialLoad: true)
self.loadComplete = true
}
}
init(url URL: URL) {
super.init(nibName: nil, bundle: nil)
self.initialURL = URL as URL!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Swift background mode for BLE iOS9

I want to improve the MPCRevisited project which is Chat app that using multi peer method. I'm using BLE to connect one device to another device (iPad and iPod) and send and receive the data. However, when I press home button to make background mode on one device, after 5 seconds, I can't send or receive the data.
image description here
I've already check all the thing in background modes, but still its not working at all.
import UIKit
import MultipeerConnectivity
class ParkBenchTimer {
let startTime:CFAbsoluteTime
var endTime:CFAbsoluteTime?
init() {
startTime = CFAbsoluteTimeGetCurrent()
}
func stop() -> CFAbsoluteTime {
endTime = CFAbsoluteTimeGetCurrent()
return duration!
}
var duration:CFAbsoluteTime? {
if let endTime = endTime {
return endTime - startTime
} else {
return nil
}
}
}
class ChatViewController: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var chatTextField: UITextField!
#IBOutlet weak var chatTableView: UITableView!
var messagesArray: [[String : String]] = []
let mpcManager = MPCManager.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.chatTableView.delegate = self
self.chatTableView.dataSource = self
self.chatTableView.estimatedRowHeight = 60.0
self.chatTableView.rowHeight = UITableViewAutomaticDimension
self.chatTextField.delegate = self
self.mpcManager.messageRecievedDelegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
// MARK: IBAction method implementation
#IBAction func endChat(sender: AnyObject) {
let messageDictionary: [String: String] = ["message": "_end_chat_"]
if self.mpcManager.sendData(dictionaryWithData: messageDictionary, toPeer: self.mpcManager.session.connectedPeers[0] as MCPeerID){
self.dismissViewControllerAnimated(true, completion: { () -> Void in
self.mpcManager.session.disconnect()
})
}
}
// MARK: UITableView related method implementation
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.messagesArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("idCell") else {
assert(true)
return UITableViewCell()
}
guard let currentMessage = self.messagesArray[safe: indexPath.row] else {
print(" ")
assert(true)
return UITableViewCell()
}
if let sender = currentMessage["sender"] {
var senderLabelText: String
var senderColor: UIColor
if sender == "self" {
senderLabelText = "I said:"
senderColor = UIColor.purpleColor()
} else {
senderLabelText = sender + " said:"
senderColor = UIColor.orangeColor()
}
cell.detailTextLabel?.text = senderLabelText
cell.detailTextLabel?.textColor = senderColor
}
if let message = currentMessage["message"] {
cell.textLabel?.text = message
}
return cell
}
// MARK: UITextFieldDelegate method implementation
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
guard let textFieldText = textField.text else {
assert(true)
return false
}
let messageDictionary: [String: String] = ["message": textFieldText]
guard let connectedPeer = self.mpcManager.session.connectedPeers[safe: 0] else {
print(" ")
assert(true)
return false
}
if self.mpcManager.sendData(dictionaryWithData: messageDictionary, toPeer: connectedPeer) {
let dictionary = ["sender": "self", "message": textFieldText]
self.messagesArray.append(dictionary)
self.updateTableview()
} else {
print("Could not send data")
}
textField.text = ""
return true
}
// MARK: Custom method implementation
func updateTableview(){
chatTableView.reloadData()
if self.chatTableView.contentSize.height > self.chatTableView.frame.size.height {
let indexPathToScrollTo = NSIndexPath(forRow: messagesArray.count - 1, inSection: 0)
self.chatTableView.scrollToRowAtIndexPath(indexPathToScrollTo, atScrollPosition: .Bottom, animated: true)
}
}
}
extension ChatViewController : MPCManagerRecievedMessageDelegate {
func managerRecievedData(data:NSData ,fromPeer:MCPeerID) {
// Convert the data (NSData) into a Dictionary object.
let dataDictionary = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [String : String]
// Check if there's an entry with the "message" key.
if let message = dataDictionary["message"] {
// Make sure that the message is other than "_end_chat_".
if message != "_end_chat_"{
// Create a new dictionary and set the sender and the received message to it.
let messageDictionary: [String: String] = ["sender": fromPeer.displayName, "message": message]
// Add this dictionary to the messagesArray array.
messagesArray.append(messageDictionary)
// Reload the tableview data and scroll to the bottom using the main thread.
self.updateTableview()
} else {
}
}
}
func managerDidRecievedMessage(message: String, fromPeer: MCPeerID) {
// Create a new dictionary and set the sender and the received message to it.
//let messageDictionary: [String: String] = ["sender": fromPeer.displayName, "message": message]
// Add this dictionary to the messagesArray array.
//messagesArray.append(messageDictionary)
// Reload the tableview data and scroll to the bottom using the main thread.
//self.updateTableview()
}
func managerDidEndChat(fromPeer:MCPeerID) {
// In this case an "_end_chat_" message was received.
// Show an alert view to the user.
let alert = UIAlertController(title: "", message: "\(fromPeer.displayName) ended this chat.", preferredStyle: UIAlertControllerStyle.Alert)
let doneAction: UIAlertAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.Default) { (alertAction) -> Void in
self.mpcManager.session.disconnect()
self.dismissViewControllerAnimated(true, completion: nil)
}
alert.addAction(doneAction)
self.presentViewController(alert, animated: true, completion: nil)
}
}
This is my code.
Please help me if someone knows this problem. What I want to do is one device to keep sending the message and other device to become background and foreground back and forth.
Thank you.
Looking at some other StackOverflow posts (here and here), it seems like the Multipeer Connectivity Framework is not built to function in the background and your functionality will disappear after a couple minutes.
Bluetooth will function in the background, with the capabilities that you checked, but you will have to create your own messaging platform; even though Multipeer relies partially on Bluetooth, the capabilities are separate entities.

Resources