First of all, I have never used notifications in an app. I have done tutorials but the whole thing confuses me.
I have created a SwiftUI file called Notify.swift. I want the user to be able to set a time for a notification to alert them to perform a task at a specified time, like in this image:
Where you see the time in the image, I have created a DatePicker to pick a time for the notification:
VStack {
Button(action: {}) {
HStack {
DatePicker(" Select a time ....",
selection: $wakeup, displayedComponents: .hourAndMinute)
.font(.title2)
.accentColor(Color(.white))
}
}.background(Color(.black))
}
.frame(width: .infinity, height: 40, alignment: .center)
.padding()
When the user clicks on the Create Button to set the notification, it should set the notification at that particular time (all the time, unless changed). This is what I need to happen but don't know how to do it:
If the notification time is set for 8:30am, like in the image, and the user selects CREATE, a notification is set and should be sent to the user to perform whatever task with maybe a sound and a message at that specified time.
I understand that there are different types of notification: local, user, Apple push, etc, but I don't know which type this falls in or how to do it.
Would this be a notification or an alarm?
You can use local notifications for that. Here I made a function for you to trigger the notification. First off all, check if that time is prior the current time. Then the notification will be tomorrow and we add one day to our Date. You can change title, body as you wish.
Make sure to wrap your DatePicker outside the button, otherwise it will always trigger a notification when you click the DatePicker.
func scheduleNotification() -> Void {
let content = UNMutableNotificationContent()
content.title = "Your title"
content.body = "Your body"
var reminderDate = wakeup
if reminderDate < Date() {
if let addedValue = Calendar.current.date(byAdding: .day, value: 1, to: reminderDate) {
reminderDate = addedValue
}
}
let comps = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: reminderDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: false)
let request = UNNotificationRequest(identifier: "alertNotificationUnique", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("Uh oh! We had an error: \(error)")
}
}
}
Also you need to request permission for Notifications like this:
func requestPush() -> Void {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
}
Here your button:
VStack {
Button(action: {
scheduleNotification()
}) {
Text("Save notification")
}
DatePicker("Select a time ....",
selection: $wakeup, displayedComponents: .hourAndMinute)
.font(.title2)
.accentColor(Color(.white))
}
Related
I've got an action function for my popup and I need to access the feature attributes from the pop up within the action function. In the code below I'd like to access {SAWID} -- I dont see it in the event parameter sent to the function.
var ContactsAction = {
title: "Get Contacts",
id: "contacts-this",
};
var template = {
// autocasts as new PopupTemplate()
title: "{Name}",
content: "{SAWID}",
actions: [ContactsAction]
};
// Event handler that fires each time an action is clicked.
view.popup.on("trigger-action", lang.hitch(this, this.Contacts));
// Executes when GetContacts is clicked in pop ups
Contacts: function (event) {
if (event.action.id === "contacts-this") {
//grab SAWID
}
}
Thanks
Pete
I found something that works, although its probably not the best way to do it:
there is an innerText property on the even.target object that includes all the text in the pop up. If I parse the innerText property I can get what I need: If anyone knows of a cleaner way please let me know. Thanks
// Executes when GetContacts is clicked in pop ups
Contacts: function (event) {
if (event.action.id === "contacts-this") {
var str = event.target.innerText;
var start = str.indexOf("Close") + 6;//"Close" always precedes my SAWID
var end = str.indexOf("Zoom") - 1;//"Zoom" is always after my SAWID
var SAWID = str.substring(start, end);
alert(SAWID);
}
}
I have an AppSwitch, which is being used to 'activate/deactivate' user accounts in my app, It's checked status is read from a database, which works as intended, changing both the switch and text depending on the bool per user.
My problem is
Each time I open the page with the AppSwitch on, it sends the onCheckedChanged signal - in turn sending a notification to the user their account has been activated? I had planned to notify users that their account was active with a push notification, but don't want this sending every time the admin moves to their page!
I am using Felgo(formerly V-Play) for development, a link to the AppSwitch documentation is:
Felgo AppSwitch
My code is:
Rectangle {
width: parent.width / 2
height: parent.height
Column {
id: column
anchors.fill: parent
AppButton {
id: groupStatusText
width: parent.width
height: parent.height / 2
}
AppSwitch {
id: editStatus
knobColorOn: "#b0ced9"
backgroundColorOn: "#81b1c2"
checked: {
//userListItem is the current profile details of the user page
if(userListItem.groupStatus === 1) {
groupStatusText.text = "Deactivate this user"
}
else if(userListItem.groupStatus === 0) {
groupStatusText.text = "Activate this user"
}
}
onCheckedChanged: {
if(editStatus.checked === true) {
//the signal which sends to my database on updating the value,
//works as intended but sends signal each time page is created?
detailPage.adminEditGroupStatus(1)
} else {
detailPage.adminEditGroupStatus(0)
}
}
}
}
Is it normal behaviour for the onCheckedChanged signal to be firing on each instance the page is loaded? or how could I amend this that it only fires when the user changes it!?
Thanks for any help!
Add a condition to be sure the signal is sent after full init of the component (including after creation of the binding on the checked property).
You can add a boolean property on the switch :
AppSwitch {
property bool loaded: false
Component.onCompleted: loaded = true
onCheckedChanged: {
if (loaded) {
// notify user
}
}
}
or simply use the state:
AppSwitch {
Component.onCompleted: state = "loaded"
onCheckedChanged: {
if (state === "loaded") {
// notify user
}
}
}
i have used ScrollView with HStack, now i need to load more data when user reached scrolling at last.
var items: [Landmark]
i have used array of items which i am appeding in HStack using ForEach
ScrollView(showsHorizontalIndicator: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
What is the best possible solution to manage load more in SwiftUI without using custom action like loadmore button.
It's better to use ForEach and List for this purpose
struct ContentView : View {
#State var textfieldText: String = "String "
private let chunkSize = 10
#State var range: Range<Int> = 0..<1
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("Load more")
}
}
}
func loadMore() {
print("Load more...")
self.range = 0..<self.range.upperBound + self.chunkSize
}
}
In this example each time you press load more it increases range of State property. The same you can do for BindableObject.
If you want to do it automatically probably you should read about PullDownButton(I'm not sure if it works for PullUp)
UPD:
As an option you can download new items by using onAppear modifier on the last cell(it is a static button in this example)
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("")
}
.onAppear {
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 10)) {
self.loadMore()
}
}
}
}
Keep in mind, that dispatch is necessary, because without it you will have an error saying "Updating table view while table view is updating). Possible you may using another async way to update the data
If you want to keep using List with Data instead of Range, you could implement the next script:
struct ContentView: View {
#State var items: [Landmark]
var body: some View {
List {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
.onAppear {
checkForMore(landmark)
}
}
}
}
func checkForMore(_ item: LandMark) {
guard let item = item else { return }
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
// function to request more data
getMoreLandMarks()
}
}
}
Probably you should work in a ViewModel and separate the logic from the UI.
Credits to Donny Wals: Complete example
I am trying to implement the action from notification. And so far I am able to trigger the right delegate functions, but after tapping the app is not brought to the foreground.
Relevant code:
#available(iOS 10.0, *)
func registerCategory() -> Void{
print("register category")
let callNow = UNNotificationAction(identifier: "call", title: "Call now", options: [])
let clear = UNNotificationAction(identifier: "clear", title: "Clear", options: [])
let category : UNNotificationCategory = UNNotificationCategory.init(identifier: "IDENT123", actions: [callNow, clear], intentIdentifiers: [], options: [])
let center = UNUserNotificationCenter.currentNotificationCenter()
center.setNotificationCategories([category])
}
#available(iOS 10.0, *)
func scheduleNotification(event : String, interval: NSTimeInterval) {
print("schedule ", event)
let content = UNMutableNotificationContent()
content.title = event
content.body = "body"
content.categoryIdentifier = "CALLINNOTIFICATION"
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: interval, repeats: false)
let identifier = "id_"+event
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
let center = UNUserNotificationCenter.currentNotificationCenter()
center.addNotificationRequest(request) { (error) in
}
}
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, willPresentNotification notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
print("willPresent")
completionHandler([.Badge, .Alert, .Sound])
}
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, didReceiveNotificationResponse response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
let notification: UNNotification = response.notification
let UUID = notification.request.content.userInfo["UUID"] as! String
switch (response.actionIdentifier) {
case "COMPLETE":
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers([UUID])
case "CALLIN":
let call = Call()
CalendarController.sharedInstance.fetchMeetingByUUID(UUID, completion: { (thisMeeting) -> Void in
if(!CallIn.Yield(thisMeeting).ConferenceCallNumber.containsString("None")){
call._call(thisMeeting)
}else{
//will open detail view, in case that no number were detected
NSNotificationCenter.defaultCenter().postNotificationName("OpenDetailViewOfMeeting", object: self, userInfo: ["UUID":UUID])
}
})
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers([UUID])
default: // switch statements must be exhaustive - this condition should never be met
log.error("Error: unexpected notification action identifier: \(UUID)")
}
completionHandler()
}
I am able to hit the delegate function didReceiveNotificationResponse() with a breakpoint, and it does some actions that I put there, but not in a way that is expected (It has to start a device-call, instead it just dismisses notifications list, and nothing happens, however when I manually open the app again, the call starts as if there is no permission to open the app from notification).
I found out the reason myself, so this might be helpful to someone in the future. The answer turned out to be quite simple. When creating an action of the notification, there is this parameter: options. When you register category, you need to put it either way .Foreground or .Destructive like this:
func reisterCategory () {
let callNow = UNNotificationAction(identifier: NotificationActions.callNow.rawValue, title: "Call now", options: UNNotificationActionOptions.Foreground)
let clear = UNNotificationAction(identifier: NotificationActions.clear.rawValue, title: "Clear", options: UNNotificationActionOptions.Destructive)
let category = UNNotificationCategory.init(identifier: "NOTIFICATION", actions: [callNow, clear], intentIdentifiers: [], options: [])
let center = UNUserNotificationCenter.currentNotificationCenter()
center.setNotificationCategories([category])
}
I want to show an image as a toast instead of plain text message.
I have tried:
try{
var toast = Titanium.UI.createNotification({
duration: Ti.UI.NOTIFICATION_DURATION_LONG,
background: '/images/img1.png'
});
toast.show();
}
catch (err)
{
alert(err.message);
}
Application gets crashed without giving any alert. I have also tried :
try{
var toast = Titanium.UI.createNotification({
duration: Ti.UI.NOTIFICATION_DURATION_LONG,
message: 'text',
});
toast.setBackgroundImage('/images/img1.png');
toast.show();
}
catch (err)
{
alert(err.message);
}
But same issue. App crashes without giving error alert. Anyone knows how to give image in toast?
I think you missed '..' in the background image path.
/images/img1.png should be: ../images/img1.png
I solved it by the functions below. I have decide fade-out time as per my requirement (i.e. 10% of total time). This code may need to handle back button pressed event manually.
var createImageToast = function (img, time)
{
Ti.UI.backgroundColor = 'white';
var win = Ti.UI.createWindow();
var image = Ti.UI.createImageView({
image: img,
});
win.add(image);
win.open();
setTimeout(function(){
decreaseImageOpacity(win,image,1,parseInt(time/10));
},parseInt(time*9/10));
}
var decreaseImageOpacity = function (win, image, opacity, time)
{
if(opacity<=0)
{
win.close();
}
else
{
setTimeout(function(){
image.setOpacity(''+opacity);
decreaseImageOpacity(win,image,opacity-0.1, time);
},parseInt(time/10));
}
}