Check if battery charging status changes in real time Swift - ios

I want to check if my iphone is charging or not and what percentage. Im able to get the percentage fine as well as knowing if its plugged in or not when i start the app, however im not able ot make it change status as i unplug the charger. Essentially i want to be able plug and unplug the phone and have my Charge.text field change status from "charging" to "unplugged" and vice versa.
i implemented an alert box but that also only pops up when i start the app. ive got the core location in there because ive got an idea on implementing a location feedback thing, but its not used now, please just ignore that.
when searching the forums i seemingly only get answers on on how to check it once, not continuous updates. i dont know if the "real time" description helps but i didnt really know how to describe it. please help :)
import UIKit
import CoreLocation
import Alamofire
import SwiftyJSON
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var Charge: UILabel!
#IBOutlet weak var resultsField: UILabel!
var chargingVar = ""
var batteryLevel: Float {
return UIDevice.current.batteryLevel
}
func batteryStatus() {
UIDevice.current.isBatteryMonitoringEnabled = true
batteryStateChanged()
switch UIDevice.current.batteryState {
case .unknown:
self.Charge.text = "BATTERY: UNKNOWN!"
case .charging:
self.Charge.text = "Charging..."
case .full:
self.Charge.text = "BATTERY full"
case .unplugged:
self.Charge.text = "unplugged"
default:
self.Charge.text = "nope"
}
}
func batteryStateChanged(){
if (UIDevice.current.batteryState == .charging) { UIApplication.shared.isIdleTimerDisabled = true
self.chargingVar = "is charging. \u{1F603}"
chargingAlaer()
}else{
self.chargingVar = "is not charging. \u{1F622} "
chargingAlaer()
}
}
func chargingAlaer(){
let alertController = UIAlertController(title: "Charging Status",
message: "Your device \(chargingVar)",
preferredStyle: UIAlertController.Style.alert)
let ok = UIAlertAction(title: "OK",
style: UIAlertAction.Style.default,
handler: {(action) -> Void in
})
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
#objc func displayBatteryCharge() {
self.resultsField.text = "\(batteryLevel * 100)%"
}
override func viewDidLoad() {
super.viewDidLoad()
UIDevice.current.isBatteryMonitoringEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidAppear(_ animated: Bool) {
displayBatteryCharge()
batteryStatus()
}
}

Basically you did everything to display the current status, now you need to add something that is notified, or checks regularly for said status.
In this case, I believe the best way would be to register to the batteryStateDidChangeNotification notification, which is only sent by the system when isBatteryMonitoringEnabled is set to true, as you did in the viewDidLoad
I think adding
NotificationCenter.default.addObserver(
self,
selector: #selector(batteryStatus),
name: UIDevice.batteryStateDidChangeNotification,
object: nil
)
Should do the trick. In the viewDidLoad for instance.

Related

How to update user's current lat long to API server?

I have a project in which I need to update the user's current location coordinates on the API server. How can I achieve this? Is continuously calling the API a good idea (I think no)?
I need to update coordinates continuously so other users can see me like in the Uber app the very first time we can see drivers near me.
Maybe you can implement a timer, and make a POST request by sending your lat long periodically.
class ViewController: UIViewController {
var timer: Timer!
override func viewDidLoad() {
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(makeRequest), userInfo: nil, repeats: true)
}
#objc func makeRequest() {
// make your post request here.
}
}
For example the timer given above will call makeRequest method every 10 seconds.
If you don't know much about networking, make a research on the following topics:
Swift Networking Layer, Alamofire, URLSession
**Step 1 :- Get latitude and long. of the user first.**
Reference URL:- https://stackoverflow.com/questions/12736086/how-to-get-location-latitude-longitude-value-in-variable-on-ios
**Step 2:- Define following variables globally.**
var timer:Timer?
var sourceLatitude = CLLocationDegrees()
var sourceLongitude = CLLocationDegrees()
**Step 3:- Setup timer for continuous update as below.**
override func viewDidLoad()
{
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 15, target: self, selector: #selector(self.updateDriverLoction), userInfo: nil, repeats: true)
}
**Step 4:- call appropriate method for sending and updating lat. long. to the server using any of networking API.(In my case I have used Alamofire)**
// must be internal or public.
#objc func updateDriverLoction() {
if Reachability.isConnectedToNetwork() {
let param1:[String:String] = [
"latitude" : "\(sourceLatitude)",
"longitude" : "\(sourceLongitude)"
]
ServerClass.sharedInstance.putRequestWithUrlParameters(param1, path: BASE_CAB_URL + PROJECT_URL.UPDATE_DRIVER_LOCATION_API, successBlock: { (json) in
print(json)
let success = json["success"].stringValue
if success == "true" {
}
else {
}
}, errorBlock: { (NSError) in
// UIAlertController.showInfoAlertWithTitle("Alert", message: kUnexpectedErrorAlertString, buttonTitle: "Okay")
})
}
}
else{
UIAlertController.showInfoAlertWithTitle("Alert", message: "Please Check internet connection", buttonTitle: "Okay")
}
}

How do you present a sequence of UIAlertControllers?

I have an app that can download many publications from a server at once. For each publication that already exists in the app, I want to prompt the user if he wants to overwrite the existing version.
Is there any clean way to present UIAlertControllers so that when the user has answered one, the app presents the next one?
Here is the output
Though two alert actions were called in a subsequent statements, second alert will be shown only after user interacts with alert on screen I mean only after tapping ok or cancel.
If this is what you want, as I mentioned in my comment you can make use of Asynchronous Operation and Operation Queue with maximum concurrent operation as 1
Here is the code.
First declare your own Asynchronous Operation
struct AlertObject {
var title : String! = nil
var message : String! = nil
var successAction : ((Any?) -> ())! = nil
var cancelAction : ((Any?) -> ())! = nil
init(with title : String, message : String, successAction : #escaping ((Any?) -> ()), cancelAction : #escaping ((Any?) -> ())) {
self.title = title
self.message = message
self.successAction = successAction
self.cancelAction = cancelAction
}
}
class MyAsyncOperation : Operation {
var alertToShow : AlertObject! = nil
var finishedStatus : Bool = false
override init() {
super.init()
}
override var isFinished: Bool {
get {
return self.finishedStatus
}
set {
self.willChangeValue(forKey: "isFinished")
self.finishedStatus = newValue
self.didChangeValue(forKey: "isFinished")
}
}
override var isAsynchronous: Bool{
get{
return true
}
set{
self.willChangeValue(forKey: "isAsynchronous")
self.isAsynchronous = true
self.didChangeValue(forKey: "isAsynchronous")
}
}
required convenience init(with alertObject : AlertObject) {
self.init()
self.alertToShow = alertObject
}
override func start() {
if self.isCancelled {
self.isFinished = true
return
}
DispatchQueue.main.async {
let alertController = UIAlertController(title: self.alertToShow.title, message: self.alertToShow.message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.alertToShow.successAction(nil) //pass data if you have any
self.operationCompleted()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
self.alertToShow.cancelAction(nil) //pass data if you have any
self.operationCompleted()
}))
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
func operationCompleted() {
self.isFinished = true
}
}
Though code looks very complicated in essence its very simple. All that you are doing is you are overriding the isFinished and isAsynchronous properties of Operation.
If you know how Operation queues works with Operation it should be very clear as to why am I overriding these properties. If in case u dont know! OperationQueue makes use of KVO on isFinished property of Operation to start the execution of next dependent operation in Operation queue.
When OperationQueue has maximum concurrent operation count as 1, isFinished flag of Operation decides when will next operation be executed :)
Because user might take action at some different time frame on alert, making operation Asynchronous (By default Operations are synchronous) and overriding isFinised property is important.
AlertObject is a convenience object to hold alert's meta data. You can modify it to match your need :)
Thats it. Now whenever whichever viewController wants to show alert it can simply use MyAsyncOperation make sure you have only one instance of Queue though :)
This is how I use it
let operationQueue = OperationQueue() //make sure all VCs use the same operation Queue instance :)
operationQueue.maxConcurrentOperationCount = 1
let alertObject = AlertObject(with: "First Alert", message: "Success", successAction: { (anything) in
debugPrint("Success action tapped")
}) { (anything) in
debugPrint("Cancel action tapped")
}
let secondAlertObject = AlertObject(with: "Second Alert", message: "Success", successAction: { (anything) in
debugPrint("Success action tapped")
}) { (anything) in
debugPrint("Cancel action tapped")
}
let alertOperation = MyAsyncOperation(with: alertObject)
let secondAlertOperation = MyAsyncOperation(with: secondAlertObject)
operationQueue.addOperation(alertOperation)
operationQueue.addOperation(secondAlertOperation)
As you can see I have two alert operations added in subsequent statement. Even after that alert will be shown only after user dismisses the currently displayed alert :)
Hope this helps
Althought answer with Queue is very good, you can achieve te same as easy as:
var messages: [String] = ["first", "second"]
func showAllerts() {
guard let message = messages.first else { return }
messages = messages.filter({$0 != message})
let alert = UIAlertController(title: "title", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] (action) in
// do something
self?.showAllerts()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (action) in
self?.showAllerts()
}))
present(alert, animated: true, completion: nil)
}
(replace array of messages with whatever you want)
I would recommend creating a Queue data structure (https://github.com/raywenderlich/swift-algorithm-club/tree/master/Queue).
Where alert objects are queued in the order that the alerts are initialized. When a user selects an action on one of the alerts, dequeue the next possible alert and present it.
I had the same problem in my app and tried several solutions, but all of them were messy. Then I thought of a very simple and effective way: use a delay to retry presentation until it can be shown. This approach is much cleaner in that you don't need coordinated code in multiple places and you don't have to hack your action handlers.
Depending on your use case, you might care that this approach doesn't necassarily preserve the order of the alerts, in which case you can easily adapt it to store the alerts in an array to preserve order, showing and removing only the first on in the array each time.
This code overrides the standard presentation method in UIViewController, use it in your subclass which is presenting the alerts. This could also be adapted to an app level method if needed that descends from the rootViewController to find the top most presented VC and shows from there, etc.
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
// cannot present if already presenting.
if (self.presentedViewController) {
// cannot present now, try again in 100ms.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// make sure we ourselve are still presented and able to present.
if (self.presentingViewController && !self.isBeingDismissed) {
// retry on self
[self presentViewController:viewControllerToPresent animated:flag completion:completion];
}
});
} else {
// call super to really do it
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
}
Few years ago I wrote a kind of presentation service, that process queue of items to present. When there isn't any presented view in current moment it take another one from array of items to present.
Maybe it will help someone:
https://github.com/Flawion/KOControls/blob/master/Sources/KOPresentationQueuesService.swift
Usage is very simple:
let itemIdInQueue = present(viewControllerToPresent, inQueueWithIndex: messageQueueIndex)

Check if In-App Purchase can be made before displaying the button

I'm implementing In-App purchases in an XCode project, and everything works fine except for one error. When the user isn't connected to the internet and he clicks a purchase button, the app crashes. I believe this happens because the in-app purchases haven't been fetched from iTunes and clicking the button can't run the purchase process. This crash also happens when the user clicks the button on the first second that the shop screen loads, because – I think – some time is needed to fetch (or request) the products. Here's what I'm talking about:
override func didMove(to view: SKView) {
...
fetchAvailableProducts()
}
func fetchAvailableProducts() {
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects:
productID100,
productID250,
productID500,
productIDRemoveAds,
productIDUnlockAll)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
I'm basing my code on this tutorial.
Is there a way to change my code to make it "crash-proof", so that it first checks if the products can be bought to let you use the button?
I use SwiftyStorkeKit ( https://github.com/bizz84/SwiftyStoreKit ) , which uses a completion handler to fill in the products (will save you from reinventing the wheel as well - and is a great resource to learn from)
As for checking network connection, I use Apple's Reachability ( https://developer.apple.com/library/content/samplecode/Reachability/Introduction/Intro.html ). Here is the relevant parts of an implementation. It also check in situations where the app loses focus. You can also use the network check before any store operation.
class vcon: UIViewController {
#IBOutlet weak var noConnectionView: UIView!
func isNetworkAvailable() -> Bool {
//quick test if network is available
var netTest:Reachability? = Reachability(hostName: "apple.com")!
if netTest?.currentReachabilityStatus == .notReachable {
netTest = nil
return false
}
netTest = nil
return true
}
func displayNoNetworkView() {
//this example pulls from a storyboard to a view I have in front of everything else at all times, and shows the view to block everything else if the network isnt available
let ncvc = UIStoryboard(name: "HelpPrefsInfo", bundle: nil).instantiateViewController(withIdentifier: "noNetworkVC") as! noNetworkVC
ncvc.view.frame = noConnectionView.bounds
ncvc.view.backgroundColor = color03
ncvc.no_connection_imageView.tintColor = color01
ncvc.noInternetConnection_label.textColor = color01
noConnectionView.addSubview(ncvc.view)
}
func hideDataIfNoConnection() {
//the actual code that displays the no net connection view
if !isNetworkAvailable() {
if noConnectionView.isHidden == true {
noConnectionView.alpha = 0
noConnectionView.isHidden = false
self.iapObjects = []
UIView.animate(withDuration: 0.50, animations: {
self.noConnectionView.alpha = 1
}, completion:{(finished : Bool) in
});
}
} else {
if noConnectionView.isHidden == false {
self.collection_view.reloadData()
UIView.animate(withDuration: 0.50, animations: {
self.noConnectionView.alpha = 0
}, completion:{(finished : Bool) in
self.noConnectionView.isHidden = true
self.loadIAPData()
});
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationDidBecomeActive, object: nil)
displayNoNetworkView()
loadIAPData()
}
func loadIAPData() {
load the data if the network is available
if isNetworkAvailable() {
helper.requestProductsWithCompletionHandler(completionHandler: { (success, products) -> Void in
if success {
self.iapObjects = products!
self.collection_view.reloadData()
} else {
let alert = UIAlertController(title: "Error", message: "Cannot retrieve products list right now.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
})
}
}
func willEnterForeground() {
hideDataIfNoConnection()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
hideDataIfNoConnection()
}

swift 2 block the running of program if location is off

I want to check and see if the location is on and if it's not or user didn't give permission to use location, app quits (won't run).
Is there a way to do this as people are saying using exit(0) is not recommended and will make Apple sad. :D
you could put a view onto the screen (full width and height) with a label that tells the user that it's only possible to use the app with location services enabled. of course the user should not be possible to interact with this view in any way.
here is an example helper class:
import UIKit
import CoreLocation
class LocationHelper: NSObject, CLLocationManagerDelegate {
private static let sharedInstance = LocationHelper()
private var locationManager: CLLocationManager! {
didSet {
locationManager.delegate = self
}
}
private override init() {}
class func setup() {
sharedInstance.locationManager = CLLocationManager()
}
private func informUserToEnableLocationServices() {
let infoPopup = UIAlertController(title: "Location Services", message: "Sorry, but you have to enable location services to use the app...", preferredStyle: .Alert)
let tryAgainAction = UIAlertAction(title: "Try again", style: .Default) { (action) in
if CLLocationManager.authorizationStatus() != .AuthorizedWhenInUse {
self.informUserToEnableLocationServices()
}
}
infoPopup.addAction(tryAgainAction)
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let rootViewController = appDelegate?.window?.rootViewController
rootViewController?.presentViewController(infoPopup, animated: true, completion: nil)
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
locationManager.requestWhenInUseAuthorization()
case .AuthorizedWhenInUse:
break
default:
informUserToEnableLocationServices()
}
}
}
simply call LocationHelper.setup() after the app launched and the class should handle the rest...
Apple does not like exit(0) for a reason. I would highly recommend not terminating the app yourself. Maybe you could let the user use the app with limited features? Another option would be to make an alert with no actions, or actions that don't do anything.

Swift: Make an error handler of AVAudioPlayer

I am using AVAudioPlayer for my app, and when the view controller loads the audio plays automatically as the view loads but how do I make a statement if the audio cannot find that file and when it can't find the file I want an alert message to pop up saying the audio cannot be found.
Code:
var euphoriaAudio = try! AVAudioPlayer(contentsOfURL:
NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Euphoria", ofType: "mp3")!))
Note: This variable declaration is not in any function. It is outside sitting alone in the class which works perfectly fine.
Should do everything you described.
class EuphoriaViewController: UIViewController {
var player: AVAudioPlayer?
private func showAlert(message: String) {
let alert = UIAlertController(title: "Warning",
message: message,
preferredStyle: .Alert)
let ok = UIAlertAction(title: "OK", style: .Default) { action in
// Execute some code upon OK tap here if you'd like
}
alert.addAction(ok)
presentViewController(alert, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let fileURL = NSBundle.mainBundle().URLForResource("Euphoria",withExtension: "mp3") else {
showAlert("Can't find Euphoria.mp3 resource")
return
}
do {
player = try AVAudioPlayer(contentsOfURL: fileURL)
player?.prepareToPlay()
}
catch {
showAlert("Can't load Euphoria.mp3 resource")
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
player?.play()
}
}
Next time, try it yourself and post questions like - I did this, but it doesn't work, here's the code, ...
If you don't know how to display alert, ... You should start with Start developing iOS Apps Today, About iOS App Architecture, iOS HIG, ...
Also you should read How do I ask a good question to get your questions answered in the future. So far you have 10 questions, some of them answered, 3 of them with accepted answer, ... Try to ask better ...

Resources