how to implement multiple local notification not a single notification for swift 3 - notifications

i have use the single notification , and this is my code:
this is for register the local notification>>>
func registerLocal() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Yay!")
} else {
print("D'oh")
}
}
}
and this function to schedule the local notification>>>
func scheduleLocal() {
registerCategories()
let center = UNUserNotificationCenter.current()
// not required, but useful for testing!
center.removeAllPendingNotificationRequests()
let content = UNMutableNotificationContent()
content.title = "good morning"
content.body = "ttt123"
content.categoryIdentifier = "alarm"
content.userInfo = ["customData": "fizzbuzz"]
content.sound = UNNotificationSound.default()
var dateComponents = DateComponents()
dateComponents.hour = 23
dateComponents.minute = 18
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
func registerCategories() {
let center = UNUserNotificationCenter.current()
center.delegate = self
let show = UNNotificationAction(identifier: "show", title: "Tell me more…", options: .foreground)
let category = UNNotificationCategory(identifier: "alarm", actions: [show], intentIdentifiers: [])
center.setNotificationCategories([category])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// pull out the buried userInfo dictionary
let userInfo = response.notification.request.content.userInfo
if let customData = userInfo["customData"] as? String {
print("Custom data received: \(customData)")
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
// the user swiped to unlock; do nothing
print("Default identifier")
case "show":
print("Show more information…")
break
default:
break
}
}
// you need to call the completion handler when you're done
completionHandler()
}
now how can i use this code with the multiple local notification with iOS 10 and different times
thank you .

Use a different request identifier for each notification (otherwise you only see the last notification).
In the above example, ensure request identifier "UUID().uuidString" contains a unique value for each notification request.

You can call the func scheduleLocal() multiple times with different dateComponents to schedule on different dates. Or you can pass a array of dates to this function and run a loop to schedule the notifications according to these dates.
Just make sure that the identifier you pass in UNNotificationRequest(identifier:, content:, trigger:) function is different for each notification.
Hope this helps. :)

Call this function with different parameters. Like i called when app goes background
func applicationDidEnterBackground(_ application: UIApplication) {
setNotification(time: 25,identifier: "notific2")
setNotification(time: 50,identifier: "notific3")
setNotification(time: 90,identifier: "notific4")
}
func setNotification (time : Int,identifier : String)
{
let content = UNMutableNotificationContent()
content.title = "Don't forget"
content.body = "Buy some milk"
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(time),
repeats: false)
let center = UNUserNotificationCenter.current()
// Swift
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if error != nil {
// Something went wrong
}
})
}

Note that your code should works as expected! however, there is a little pitfall.
I think that I almost faced the exact same issue, after I tried to trace the pending notifications, I noted that the only notification that has been added is the last requested one. That's because you are calling in scheduleLocal() function:
// not required, but useful for testing!
center.removeAllPendingNotificationRequests()
Well, it is good to remove any existing notification reminder requests, this would help preventing unnecessary duplicate notifications, but you should call it only once before calling scheduleLocal():
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
// now you could call this function many times...
scheduleLocal()
simply, you don't want to delete each pending notification directly after adding it, instead you delete the whole previous pending notifications and add new ones.

If you want to use multiple local notification than you have to used different identifier for each request.
let request = UNNotificationRequest(identifier: "Stock Changed", content: content, trigger: nil)
Change "Stock Changed" with your different identifier for each request.
You can use loop for multiple request.

Related

Audio session .ended isn't called with two AVPlayers

Here are steps to reproduce:
Activate AVAudioSession with .playback category.
Register for AVAudioSession.interruptionNotification
Create two AVPlayers and start them
Interrupt playback by calling Siri/receiving a call by Skype, Cellular and etc.
Expected behavior:
Receiving notification of the audio session interruption with .began state at the start and .ended at the end. Also, as a side effect, Siri doesn't respond to commands.
Real behavior:
Only .began notification is called.
To bring back .ended notification (which is used to continue playback) remove one player.
Question: how to handle the audio session interruption with more than 1 AVPlayer running?
Here I created a simple demo project: https://github.com/denis-obukhov/AVAudioSessionBug
Tested on iOS 14.4
import UIKit
import AVFoundation
class ViewController: UIViewController {
private let player1: AVPlayer? = {
$0.volume = 0.5
return $0
}(AVPlayer())
private let player2: AVPlayer? = {
$0.volume = 0.5
return $0 // return nil for any player to bring back .ended interruption notification
}(AVPlayer())
override func viewDidLoad() {
super.viewDidLoad()
registerObservers()
startAudioSession()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player1?.replaceCurrentItem(with: makePlayerItem(named: "music1"))
player2?.replaceCurrentItem(with: makePlayerItem(named: "music2"))
[player1, player2].forEach { $0?.play() }
}
private func makePlayerItem(named name: String) -> AVPlayerItem {
let fileURL = Bundle.main.url(
forResource: name,
withExtension: "mp3"
)!
return AVPlayerItem(url: fileURL)
}
private func registerObservers() {
NotificationCenter.default.addObserver(
self, selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
}
private func startAudioSession() {
try? AVAudioSession.sharedInstance().setCategory(.playback)
try? AVAudioSession.sharedInstance().setActive(true)
}
#objc private func handleInterruption(_ notification: Notification) {
print("GOT INTERRUPTION")
guard
let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else {
return
}
switch type {
case .began:
print("Interruption BEGAN")
[player1, player2].forEach { $0?.pause() }
case .ended:
// This part isn't called if more than 1 player is playing
print("Interruption ENDED")
[player1, player2].forEach { $0?.play() }
#unknown default:
print("Unknown value")
}
}
}
I just ran into the same issue, and it was driving me crazy for a few days. I'm using two AVQueuePlayer (a subclass of AVPlayer) to play two sets of audio sounds on top of each other, and I get the AVAudioSession.interruptionNotification value of .began when there is an incoming call, but there is no .ended notification when the call ends.
That said, I've found that for some reason, .ended is reliably sent if you instead use two instances of AVAudioPlayer. It also works with one instance of AVAudioPlayer mixed with another instance of AVQueuePlayer. But for some reason using two instances of AVQueuePlayer (or AVPlayer) seems to break it.
Did you ever find a solution for this? For my purposes I need queuing of tracks so I must use AVQueuePlayer, so I'll probably file a bug report with Apple.

UIView got rendered before data from API returned using SwiftyJSON and Alamofire

I want to fetch data using API request.The data is fetch using SwiftyJson and Alamofire. The problem is that the data is fetched but view gets loaded before the values are fetched.How can I Solve the problem?My code is as below:
func fetchData(){
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
if response.result.isSuccess{
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
}else{
print("Error \(String(describing: response.result.error))")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
It is important to understand that apps run multiple threads. The main thread, also called the UI thread, performs the actions for the visible parts of the app, including showing views, reacting to buttons and so on.
You cannot block the Main thread.
When you make calls to external resources like API calls or loading external images, those get executed on a separate thread. That thread is independent of the main thread. Neither thread waits for the other. The app will still react to buttons while the data is loading. What you are asking for is to prevent showing the view until the data is loaded. You can do this, but you must understand that this could take time depending on your network connection. There are two approaches you can take.
Transition to the view that shows the data but put a "Loading" element on the screen until the data loads then remove the "Loading" element then redisplay the view with the data.
Load the data before you show the view. Make the previous view load the data then segue to the view that has to show the data.
You must also decide if this data loads ONCE or every time the view is displayed. By placing the call in viewDidLoad, the API call will only happen once until the app is restarted. If you want the data to load every time the screen is shown, put the call in viewWillAppear.
// Approach #1
func fetchData(){
self.showSpinner()
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
self.hideSpinner()
if response.result.isSuccess {
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
// Actually update the relevant screen elements here.
} else {
print("Error \(String(describing: response.result.error))")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//fetchData()
}
override func viewWillAppear() {
super.viewWillAppear()
fetchData()
}
func showSpinner() {
// IMPLEMENT ME
}
func hideSpinner() {
// IMPLEMENT ME
}
Alamofire mathod runs asynchronously. Not in UIThread.Then you have to reload those views after completion of method run.
As Example - tableview
func fetchData(){
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
if response.result.isSuccess{
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
// In here you have to reload, set your uiviews or all calculation
tableview.reloadData()
}else{
print("Error \(String(describing: response.result.error))")
}
}
}

Objective-C semantic equivalent written in Swift [duplicate]

The Apple documentation shows an unsettling blank space under the 'Creating a Dictionary' section of the UIKit reference here.
Has anyone found a replacement for the NSDictionaryOfVariableBindings macro, or are we expected to just write our own?
EDIT - According to this perhaps the right approach is to write a global function to handle this? Looks like complex macros are out entirely.
According to Apple source code:
NSDictionaryOfVariableBindings(v1, v2, v3) is equivalent to [NSDictionary dictionaryWithObjectsAndKeys:v1, #"v1", v2, #"v2", v3, #"v3", nil];
So in Swift you can do the same using:
let bindings = ["v1": v1, "v2": v2, "v3": v3]
NSDictionaryOfVariableBindings is, as you say, a macro. There are no macros in Swift. So much for that.
Nonetheless, you can easily write a Swift function to assign string names to your views in a dictionary, and then pass that dictionary into constraintsWithVisualFormat. The difference is that, unlike Objective-C, Swift can't see your names for those views; you will have to let it make up some new names.
[To be clear, it isn't that your Objective-C code could see your variable names; it's that, at macro evaluation time, the preprocessor was operating on your source code as text and rewriting it — and so it could just use the text of your variable names both inside quotes (to make strings) and outside (to make values) to form a dictionary. But with Swift, there is no preprocessor.]
So, here's what I do:
func dictionaryOfNames(arr:UIView...) -> Dictionary<String,UIView> {
var d = Dictionary<String,UIView>()
for (ix,v) in arr.enumerate(){
d["v\(ix+1)"] = v
}
return d
}
And you call it and use it like this:
let d = dictionaryOfNames(myView, myOtherView, myFantasicView)
myView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[v2]|", options: nil, metrics: nil, views: d)
)
The catch is that it is up to you to realize that the name for myOtherView in your visual format string will be v2 (because it was second in the list passed in to dictionaryOfNames()). But I can live with that just to avoid the tedium of typing out the dictionary by hand every time.
Of course, you could equally have written more or less this same function in Objective-C. It's just that you didn't bother because the macro already existed!
That functionality is based on macro expansion which is currently not supported in Swift.
I do not think there is any way to do something similar in Swift at the moment. I believe you cannot write your own replacement.
I'm afraid you'll have to manually unroll the dictionary definition, even if it means repeating each name twice.
So I hacked something together which seems to work:
func dictionaryOfVariableBindings(container: Any, views:UIView...) -> Dictionary<String, UIView> {
var d = Dictionary<String, UIView>()
let mirror = Mirror(reflecting: container)
let _ = mirror.children.compactMap {
guard let name = $0.label, let view = $0.value as? UIView else { return }
guard views.contains(view) else { return }
d[name] = view
}
return d
}
Usage:
let views = dictionaryOfVariableBindings(container: self, views: imageView)
ObjC runtime to the rescue!
i created an alternate solution, but it only works if each of the views are instance variables of the same object.
func DictionaryOfInstanceVariables(container:AnyObject, objects: String ...) -> [String:AnyObject] {
var views = [String:AnyObject]()
for objectName in objects {
guard let object = object_getIvar(container, class_getInstanceVariable(container.dynamicType, objectName)) else {
assertionFailure("\(objectName) is not an ivar of: \(container)");
continue
}
views[objectName] = object
}
return views
}
can be used like this:
class ViewController: UIViewController {
var childA: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.redColor()
return view
}()
var childB: UIButton = {
let view = UIButton()
view.setTitle("asdf", forState: .Normal)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.blueColor()
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(childA)
self.view.addSubview(childB)
let views = DictionaryOfInstanceVariables(self, objects: "childA", "childB")
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[childA]|", options: [], metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[childB]|", options: [], metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[childA][childB(==childA)]|", options: [], metrics: nil, views: views))
}
}
unfortunately you still have to type the variable name in as a string, but it will at least assert if there is a typo. this definitely won't work in all situations, but helpful nonetheless
Based on https://stackoverflow.com/a/55086673/1058199 by cherpak-evgeny, this UIViewController extension assumes that the container is self, the current viewController instance.
extension UIViewController {
// Alex Zavatone 06/04/2019
// Using reflection, get the string name of the UIView properties passed in
// to create a dictionary of ["viewPropertyName": viewPropertyObject…] like
// Objective-C's NSDictionaryForVariableBindings.
func dictionaryOfBindings(_ arrayOfViews:[UIView?]) -> Dictionary<String, UIView> {
var bindings = Dictionary<String, UIView>()
let viewMirror = Mirror(reflecting: self)
let _ = viewMirror.children.compactMap {
guard let name = $0.label, let view = $0.value as? UIView else { return }
guard arrayOfViews.contains(view) else { return }
bindings[name] = view
}
return bindings
}
}
Use it like so from within your viewController:
let viewArray = [mySwitch, myField, mySpinner, aStepper, someView]
let constraintsDictionary = dictionaryOfBindings(viewArray)
Tested in Xcode 10.2.1 and Swift 4.2.
Many thanks to Cherpak Evgeny for writing it in the first place.
Once you've stored all your views as properties, you could also use reflection like so:
extension ViewController {
func views() -> Dictionary<String, AnyObject> {
var views = dictionaryOfProperties()
views.forEach {
if !($1 is UIView) {
views[$0] = nil
}
}
return views
}
}
extension NSObject {
func dictionaryOfProperties() -> Dictionary<String, AnyObject> {
var result = Dictionary<String, AnyObject>()
let mirror = Mirror(reflecting: self)
for case let(label?, value) in mirror.children {
result[label] = value as? AnyObject
}
return result
}
}

Swift JSQMessagesViewController name on top

I am displaying the sender name of the receiver. However, I don't want to display the name on every single text that the person sends. For example, if he/she sends a a message consecutively, I just want it to display the name on top of the first message. I'll provide a screenshot of my chat here:
In other words, I just want it to display the name of the user on the very 'first' message he/she sends. I tried to solve this issue, in the attributedTextForMessageBubbleTopLabelAt with no luck.
I have tried to check if the senderId of the previous message is equal to the previous senderId - and then somehow find out if it can be shown or not. But, this has resulted in many, many failed attempts with optional errors, index out of range, and just simply not working solutions.
This is the code I have right now:
let message = messages[indexPath.item]
switch message.senderId {
case FIRAuth.auth()!.currentUser!.uid:
return nil
break
default:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = NSTextAlignment.left
let attributedString = NSAttributedString(string: message.senderDisplayName,
attributes: [
NSParagraphStyleAttributeName: paragraphStyle,
NSBaselineOffsetAttributeName: NSNumber(value: 0)
])
return attributedString
break
}
This is a difficult problem at first to solve when you first look at it. What we need to do is determine if a message is the first in a set of messages. So I created a function to determine wether or not a message is the first one sent by that person.
private func firstMessageInSet(indexOfMessage: IndexPath) -> Bool {
return messages[safe: indexOfMessage.item - 1]?.senderId() == messages[safe: indexOfMessage.item]?.senderId()
}
it simply checks the pervious message's senderId to see if it is the same as the current message.
Then just call that guy in this method:
override func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString! {
guard let message = messages[safe: indexPath.item] else { return nil }
return firstMessageInSet(indexOfMessage: indexPath) ? nil : NSAttributedString(string: message.senderDisplayName)
}
This will check if its the first in the set and if it is return the senderDisplayName. Now this is using short hand but is eventually the same as
if firstMessageInSet(indexOfMessage: indexPath) {
return NSAttributedString(string: message.senderDisplayName)
} else {
return nil
}
Hope that that helps you good luck with things 🤖

Cannot call value of non-function type 'HTTPURLResponse?

It is giving me the Error: Cannot call value of non-function type 'HTTPURLResponse?'
It should be caused by #escaping but I cannot make it work in the sentence below. Please help, Swift 3.0, AlamofireImage.
There are other similar answers but cannot make them work with my code below.
func getNetworkImage(_ urlString: String, completion: #escaping ((UIImage) -> Void)) -> (ImageRequest) {
let queue = decoder.queue.underlyingQueue
let request = Alamofire.request(urlString)
let imageRequest = ImageRequest(request: request)
imageRequest.request.response(
queue: queue,
responseSerializer: Request.imageResponseSerializer(),
completionHandler: { response in
guard let image = response.result.value else {
return
}
let decodeOperation = self.decodeImage(image) { image in
completion(image)
self.cacheImage(image, urlString: urlString)
}
imageRequest.decodeOperation = decodeOperation
}
)
return imageRequest
}
The error message: Cannot call value of non-function type 'HTTPURLResponse?' is telling you that the instance variable value can not be accessed. The bad thing is that it is telling you this in the wrong line of code. That may be the reason why it was so difficult to find.
The structure of the response object has changed. You code:
guard let image = response.result.value else {
return
}
Won't work anymore because the response object is of type DefaultDataResponse not HTTPResponse...
In order to access the data/image you need to go with response.response. depending on what you want to access:
response.response?.value(forKey: "")
Check the other method calls and properties. I believe you will find the data you are looking for.