Local notification in swift 3 with new UI - swift3

I know to how to create local notification in Swift 3( I am new in this part), However, I want to create something like below image. All tutorials in the web are too old and I do not what should I do.
As you can see before extending notification , there are 2 buttons. after extending also there are 2 buttons with red and blue color.
Updated
Thanks Joern
The slide gesture only show clear. Is there any settings for showing both clear and view

The red and blue buttons are only available in iOS versions prior to iOS 10. With iOS 10 the notifications design changed. The slide gesture is used for the standard actions Clear and View. The custom actions Snooze and Confirm will be displayed when you force touch the notification or pull it down (for devices without force touch). If you are using a device with force touch the View button might not be shown.
The buttons look different now:
So, here is how you implement Local Notifications with Swift 3 / 4:
For iOS versions prior to iOS 10:
If you are supporting iOS versions prior to iOS10 you have to use the old (deprecated with iOS 10) UILocalNotification:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
registerLocalNotification()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
scheduleLocalNotification()
}
func scheduleLocalNotification() {
let localNotification = UILocalNotification()
localNotification.alertTitle = "Buy milk"
localNotification.alertBody = "Remember to buy milk from store"
localNotification.fireDate = Date(timeIntervalSinceNow: 3)
localNotification.soundName = UILocalNotificationDefaultSoundName
localNotification.category = "reminderCategory" // Category to use the specified actions
UIApplication.shared.scheduleLocalNotification(localNotification) // Scheduling the notification.
}
func registerLocalNotification() {
let reminderActionConfirm = UIMutableUserNotificationAction()
reminderActionConfirm.identifier = "Confirm"
reminderActionConfirm.title = "Confirm"
reminderActionConfirm.activationMode = .background
reminderActionConfirm.isDestructive = false
reminderActionConfirm.isAuthenticationRequired = false
let reminderActionSnooze = UIMutableUserNotificationAction()
reminderActionSnooze.identifier = "Snooze"
reminderActionSnooze.title = "Snooze"
reminderActionSnooze.activationMode = .background
reminderActionSnooze.isDestructive = true
reminderActionSnooze.isAuthenticationRequired = false
// Create a category with the above actions
let shoppingListReminderCategory = UIMutableUserNotificationCategory()
shoppingListReminderCategory.identifier = "reminderCategory"
shoppingListReminderCategory.setActions([reminderActionConfirm, reminderActionSnooze], for: .default)
shoppingListReminderCategory.setActions([reminderActionConfirm, reminderActionSnooze], for: .minimal)
// Register for notification: This will prompt for the user's consent to receive notifications from this app.
let notificationSettings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: [shoppingListReminderCategory])
UIApplication.shared.registerUserNotificationSettings(notificationSettings)
}
}
This will register the local notification and fires it 3 seconds after the user closes the app (for testing purposes)
For iOS 10 and later:
If you target your app to iOS 10 you can use the new UserNotifications framework:
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
registerUserNotifications()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
scheduleLocalNotification()
}
func registerUserNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
guard granted else { return }
self.setNotificationCategories()
}
}
func setNotificationCategories() {
// Create the custom actions
let snoozeAction = UNNotificationAction(identifier: "SNOOZE_ACTION",
title: "Snooze",
options: .destructive)
let confirmAction = UNNotificationAction(identifier: "CONFIRM_ACTION",
title: "Confirm",
options: [])
let expiredCategory = UNNotificationCategory(identifier: "TIMER_EXPIRED",
actions: [snoozeAction, confirmAction],
intentIdentifiers: [],
options: UNNotificationCategoryOptions(rawValue: 0))
// Register the category.
let center = UNUserNotificationCenter.current()
center.setNotificationCategories([expiredCategory])
}
func scheduleLocalNotification() {
let content = UNMutableNotificationContent()
content.title = "Buy milk!"
content.body = "Remember to buy milk from store!"
content.categoryIdentifier = "TIMER_EXPIRED"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
// Create the request object.
let request = UNNotificationRequest(identifier: "Milk reminder", content: content, trigger: trigger)
// Schedule the request.
let center = UNUserNotificationCenter.current()
center.add(request) { (error : Error?) in
if let theError = error {
print(theError.localizedDescription)
}
}
}
}
You can check out a demo app that uses the UserNotifications framework here

Related

SwiftUI - Publishing changes from background threads is not allowed

I am trying to create an app that creates random pictures when a button is clicked. The app is working fine but I see this message which I have never seen before."Publish changes from background threads is not allowed; make sure to publish values from the main thread.".
I am new to SwiftUI, help is appreciated.
import Foundation
import SwiftUI
class ImageviewModel{
var image: UIImage? = nil
//let url = URL(string: "https://source.unsplash.com/random/600x600")!
let url = URL(string: "https://picsum.photos/600/600")!
func responseHandler(data: Data?, response: URLResponse?) ->
UIImage?{
guard let data = data,
let image = UIImage(data: data),
let response = response else {return nil}
return image
}
func loadImageWithAsync() async throws -> UIImage?{
do{
let (data, response) = try await URLSession.shared.data(from: url,delegate: nil)
return responseHandler(data: data, response: response)
} catch{
throw error
}
}
}
class ViewModel: ObservableObject{
#Published var image: UIImage? = nil
var loader = ImageviewModel()
func fetchImage() async {
let image = try? await loader.loadImageWithAsync()
self.image = image
}
}
You need to add the MainActor wrapper to the class to guarantee that updates are done on Main
#MainActor
class ViewModel: ObservableObject{

Local Notification Sash Color in WatchOS

I'm trying to change the sash color of a local notification in my WatchKit app:
import SwiftUI
import UserNotifications
class myHostingController: WKUserNotificationHostingController<NotificationView> {
let sashColor = sashColor
}
func addNotification() {
let center = UNUserNotificationCenter.current()
let sashColor = myHostingController.sashColor
let addRequest = {
let content = UNMutableNotificationContent()
content.title = "Title content"
content.sound = UNNotificationSound.default
sashColor?.foregroundColor(.blue)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
I get the error message
Result of call to 'foregroundColor' is unused
I can't figure out how to call sashColor. Any insights would be greatly appreciated.
Customizing the sash color should be possible according to the documentation:
https://developer.apple.com/documentation/swiftui/wkusernotificationhostingcontroller/sashcolor?changes=latest_beta
https://developer.apple.com/design/human-interface-guidelines/components/system-experiences/notifications

Widget update data properly

I've made a widget which fetches Codable data and it's working just fine in the simulator ONLY. The widget updates within 30 seconds or less after the data has changed. I've set a 5 minute update limit (I understand it's called far less frequently). It's working actually really great in the simulator without any kind of background data fetches and updates in less time than I set in getTimeline. Then I ran into an issue on a a real test device.
The data won't update anywhere between 2-10+ mins while testing a real device, in the snapshot it's updated and can see the new data changes but not in the widget on springboard. I don't understand why the simulator works just fine but not a real device. The Widget is definitely being updated when the data changes but only in the Simulator so am I suppose to fetch data in the background?
I've come across this Keeping a Widget Up To Date | Apple Developer Documentation. I'm still very new to Swift and SwiftUI so this is a little bit harder for me to grasp. I'm trying to understand the section Update After Background Network Requests Complete to update my Codeable data. My guess is the simulator is different from a real device and I need to fetch data in the background for the must up to date data?
The end goal is to have the widget update as frequently as possible with the most current data. I'm not sure I even need the background data fetch?
My data model for my widget as an example (which is working fine)
class DataModel {
var data: DataClass = DataClass(results: []))
func sessions(_ completion: #escaping (DataClass -> Void) {
guard let url = URL(string: "URL HERE") else { return }
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(DataClass.self, from: data) {
self.data = response
completion(self.data)
WidgetCenter.shared.reloadTimelines(ofKind: "Widget")
}
}
}
.resume()
}
}
My getTimeline calling the data model
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let model = DataModel()
var entries: [SimpleEntry] = []
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!
let entry = SimpleEntry(date: entryDate, model: model)
entries.append(entry)
model.sessions {_ in
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
I have this for my background network request
import Foundation
import WidgetKit
class BackgroundManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
var completionHandler: (() -> Void)? = nil
private lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "widget-bundleID")
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func update() {
let task = urlSession.downloadTask(with: URL(string: "SAME URL FROM DATA MODEL HERE")!)
task.resume()
}
func urlSession(_ session: URLSession ,downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print (location)
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
self.completionHandler!()
WidgetCenter.shared.reloadTimelines(ofKind: "Widget")
print("Background update")
}
}
Then in my Widget I set .onBackgroundURLSessionEvents(). I never see any background updates or errors in the console. This seems very wrong, the Codable data will never be updated? How do I properly update my data in the background?
struct Some_Widget: Widget {
let kind: String = "Widget"
let backgroundData = BackgroundManager()
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
SomeWidget_WidgetEntryView(entry: entry)
}
.configurationDisplayName("Widget")
.description("Example widget.")
.onBackgroundURLSessionEvents { (sessionIdentifier, completion) in
if sessionIdentifier == self.kind {
self.backgroundData.update()
self.backgroundData.completionHandler = completion
print("background update")
}
}
}
}

What is the best way to add ads to a SwiftUI Grid?

Hello I want to add ads to a swiftUI grid. The grid contains pictures that I get from a firebase backend and after every couple of pictures I would like to have an ad.
I am quite new to both SwiftUi and working with ads, so I'm not sure how correct my code is, but here is what I got so far.
// Code for the pictures Grid
struct PicturesGrid: View {
private let data: [Item]
var body: some View {
let gridItems = [GridItem(.fixed(UIScreen.screenWidth / 2),
alignment: .leading),
GridItem(.fixed(UIScreen.screenWidth / 2),
alignment: .leading)]
return ScrollView(showsIndicators: false) {
LazyVGrid(columns: gridItems) {
ForEach(0..<self.data.count, id: \.self) { index in
// Using this workaround for the ad to be on the whole width of the screen
// Also, after every six images I am adding and ad
if index != 0, index % 6 == 0 {
AdView()
.frame(width: UIScreen.screenWidth, height: 280)
.padding(.top, 20)
Spacer()
item
.frame(width: UIScreen.screenWidth / 2)
} else {
item
.frame(width: UIScreen.screenWidth / 2)
}
}
}
}
}
// this is for the picture
var item: some View {
NavigationLink(destination: DetailView(viewModel: DetailViewModel(item: itemAtIndexPath))) {
Cell(viewModel: CellViewModel(item: itemAtIndexPath))
}
.buttonStyle(PlainButtonStyle())
}
}
This is the code that I am currently using to load, create and display an ad
// Code for the ad that I am currently using
struct AdView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let adController = AdViewController(self)
return adController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
class AdViewController: UIViewController {
private var adView: AdView
/// The height constraint applied to the ad view, where necessary.
var heightConstraint: NSLayoutConstraint?
/// The ad loader. You must keep a strong reference to the GADAdLoader during the ad loading
/// process.
var adLoader: GADAdLoader!
/// The native ad view that is being presented.
var nativeAdView: GADUnifiedNativeAdView!
/// The ad unit ID.
let adUnitID = "ca-app-pub-3940256099942544/3986624511"
init(_ adView: AdView) {
self.adView = adView
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
var nibView: Any?
nibView = Bundle.main.loadNibNamed("ListAdView", owner: nil, options: nil)?.first
guard let nativeAdView = nibView as? GADUnifiedNativeAdView else {
return
}
setAdView(nativeAdView)
adLoader = GADAdLoader(adUnitID: adUnitID, rootViewController: self,
adTypes: [.unifiedNative], options: nil)
adLoader.delegate = self
DispatchQueue.global(qos: .background).async {
self.adLoader.load(GADRequest())
}
}
func setAdView(_ adView: GADUnifiedNativeAdView) {
// Remove the previous ad view.
DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else {
return
}
weakSelf.nativeAdView = adView
weakSelf.view.addSubview(weakSelf.nativeAdView)
weakSelf.nativeAdView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for positioning the native ad view to stretch the entire width and height
let viewDictionary = ["_nativeAdView": weakSelf.nativeAdView!]
weakSelf.view.addConstraints(
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[_nativeAdView]|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
)
weakSelf.view.addConstraints(
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[_nativeAdView]|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
)
}
}
}
extension AdViewController: GADUnifiedNativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error:
GADRequestError) {
print("didFailToReceiveAdWithError: \(error)")
}
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADUnifiedNativeAd) {
print("Received unified native ad: \(nativeAd)")
// Deactivate the height constraint that was set when the previous video ad loaded.
heightConstraint?.isActive = false
// Populate the native ad view with the native ad assets.
// The headline and mediaContent are guaranteed to be present in every native ad.
(nativeAdView.headlineView as? UILabel)?.text = nativeAd.headline
nativeAdView.mediaView?.mediaContent = nativeAd.mediaContent
// This app uses a fixed width for the GADMediaView and changes its height to match the aspect
// ratio of the media it displays.
if let mediaView = nativeAdView.mediaView, nativeAd.mediaContent.aspectRatio > 0 {
heightConstraint = NSLayoutConstraint(
item: mediaView,
attribute: .height,
relatedBy: .equal,
toItem: mediaView,
attribute: .width,
multiplier: CGFloat(1 / nativeAd.mediaContent.aspectRatio),
constant: 0)
heightConstraint?.isActive = true
}
// This asset is not guaranteed to be present. Check that it is before
// showing or hiding it.
(nativeAdView.advertiserView as? UILabel)?.text = nativeAd.advertiser
nativeAdView.advertiserView?.isHidden = nativeAd.advertiser == nil
// In order for the SDK to process touch events properly, user interaction should be disabled.
nativeAdView.callToActionView?.isUserInteractionEnabled = false
// Associate the native ad view with the native ad object. This is
// required to make the ad clickable.
// Note: this should always be done after populating the ad views.
nativeAdView.nativeAd = nativeAd
}
}
I want to mention that this is working at the moment, but the problems that I want to fix and I don't know how are:
The grid with the pictures load, but when I scroll over an ad, it takes several seconds for the ad to load and display. How could I at least hide it while it loads or make it faster?
If I scroll over an ad, the ad loads and if I continue scrolling, when I scroll back up, the ad is not loaded anymore and I have to wait for it to load again. How can I fix this? Or what is the best practice for this kind of scenario?
Should I use multipleAds? To load them before showing? If yes, then how should I do this?
Does what I am doing here look even a little bit correct? Please...I need some help
The Best Way to show ads in SwiftUI Grids is implementing Native Ads in your app to provide personalized ad experience

fired local notification at specific time

please i have an issue about how can i trigger the local notification at specific time ? without user trigger it and need the app at specific time fired local notification
the following my code :
this is for get permission from the user
func registerLocal() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Yay!")
} else {
print("D'oh")
}
}
}
// this i schedule local notification
func scheduleLocal() {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Late wake up call"
content.body = "The early bird catches the worm, but the second mouse gets the cheese."
content.categoryIdentifier = "alarm"
content.userInfo = ["customData": "fizzbuzz"]
content.sound = UNNotificationSound.default()
var dateComponents = DateComponents()
dateComponents.hour = 3
dateComponents.minute = 19
dateComponents.day = 3
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
center.removeAllPendingNotificationRequests()
}
// her i call theses methods
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
registerLocal()
scheduleLocal()
return true
}
and when i close my app i have no receive the notification , please help about how can i trigger the local notification at specific time
thanks
You should not call center.removeAllPendingNotificationRequests() after adding your notification, since it will cancel the previously added pending notification as well. You should rather check after calling center.addRequest(request) whether your request has actually been added or not by
center.getPendingNotificationRequests(completionHandler: { pendingRequest in
print("Pending notifications: \(pendingRequest)") //Just for debugging
})
Or you can also specify a completion handler to addRequest, which will return an error if the request hasn't been added succesfully:
center.add(request, withCompletionHandler: { error in
print(error)
})