Swift: Random notifications is not random - swift

I have local notifcations working in my Apple Watch app. Setting the interval in houser and the save buttonis also working. The only thing that is not working is displaying a random message. It selects one of the three from the randomText() function and then one is repeated every "interval" time...
This is one file.
import SwiftUI
import UserNotifications
struct Nottie2: View {
#AppStorage("notificationInterval") var notificationInterval: Int = 1
#AppStorage("isSnoozed") var isSnoozed: Bool = false
#AppStorage("isNotificationsEnabled") var isNotificationsEnabled: Bool = false
#State private var borderColor = Color.orange
#State private var buttonText = "Save"
var body: some View {
VStack {
Toggle(isOn: $isNotificationsEnabled) {
if isNotificationsEnabled {
Text("Turn off")
}else {
Text("Turn on")
}
}
.padding()
.onChange(of: isNotificationsEnabled) { enabled in
if enabled {
requestPermission()
} else {
disableNotification()
}
}
if isNotificationsEnabled {
Picker("Notification Interval", selection: $notificationInterval) {
ForEach(1...6, id: \.self) { interval in
Text("\(interval) hour\(interval > 1 ? "s" : "")")
}
}.frame(height: 60)
.padding()
Button(action: {
enableNotification()
self.buttonText = "Saving"
self.borderColor = .green
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.buttonText = "Saved"
self.borderColor = .green
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
self.buttonText = "Save"
self.borderColor = .orange
}
})
{
Text(buttonText)
}.foregroundColor(.white)
.padding(1)
.frame(width: 75)
.padding(7)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(borderColor, lineWidth: 2))
.buttonStyle(.plain)
}
}
.onAppear() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Permission granted")
} else {
print("Permission denied")
isNotificationsEnabled = false
}
}
}
.onReceive(NotificationCenter.default.publisher(for: WKExtension.applicationDidBecomeActiveNotification)) { _ in
if isSnoozed {
enableNotification(snooze: true)
}
}
}
function to request the notification permissions
private func requestPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Permission granted")
enableNotification()
} else {
print("Permission denied")
isNotificationsEnabled = false
}
}
}
randomText() is called. I think that the issue is somewhere here. I think it (I do not know how) clear the notification after it is dismissed
private func enableNotification(snooze: Bool = false) {
let content = UNMutableNotificationContent()
// content.title = "Notification Title"
content.body = randomText()
content.sound = UNNotificationSound.default
var trigger: UNNotificationTrigger
if snooze {
trigger = UNTimeIntervalNotificationTrigger(timeInterval: 540, repeats: false)
} else {
trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(notificationInterval * 3600), repeats: true)
}
let request = UNNotificationRequest(identifier: "notification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
Function with an small array with random notifications texts.
func randomText() -> String {
let words = ["Place", "Cat", "House"]
return words[Int(arc4random_uniform(UInt32(words.count)))]
}
Rest of the notification actions.
private func disableNotification() {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notification"])
}
private func snoozeNotification() {
isSnoozed = true
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notification"])
enableNotification(snooze: true)
}
private func dismissNotification() {
isSnoozed = false
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notification"])
}
private func showNotificationActions() {
let snoozeAction = UNNotificationAction(identifier: "snooze", title: "Snooze", options: [])
let dismissAction = UNNotificationAction(identifier: "dismiss", title: "Dismiss", options: [.destructive])
let category = UNNotificationCategory(identifier: "notificationActions", actions: [snoozeAction, dismissAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
}
}
struct Nottie2_Previews: PreviewProvider {
static var previews: some View {
Nottie2()
}
}

This code works:
import SwiftUI
import UserNotifications
struct NotifyView: View {
#AppStorage("isNotificationsEnabled") var isNotificationsEnabled: Bool = false
#State private var interval = 1
#State private var buttonText = "Save"
#State private var borderColor = Color.orange
let messages = [
"Take a 5 minute walk.",
"Drink a glass of water.",
"Stretch your legs.",
"Take deep breaths.",
"Close your eyes and meditate for a minute.",
"Do a few jumping jacks.",
"Think of something you're grateful for.",
"Write down your thoughts in a journal.",
"Read a book for 5 minutes.",
"Call a friend and say hello.",
"Smile and relax your shoulders.",
"Make a cup of tea and take a break.",
]
var body: some View {
VStack {
Text("Notifications:")
.foregroundColor(.orange)
.fontWeight(.semibold)
Toggle(isOn: $isNotificationsEnabled) {
if isNotificationsEnabled {
Text("Turn off")
}else {
Text("Turn on")
}
}
.padding()
if isNotificationsEnabled {
Picker("Interval (hours)", selection: $interval) {
ForEach(1...10, id: \.self) { i in
Text("\(i) hours")
}
}
.pickerStyle(.wheel)
.padding()
.frame(height: 70)
Button(action: {
scheduleNotifications()
self.buttonText = "Saving"
self.borderColor = .green
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.buttonText = "Saved"
self.borderColor = .green
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
self.buttonText = "Save"
self.borderColor = .orange
}
})
{
Text(buttonText)
}
.foregroundColor(.white)
.padding(1)
.frame(width: 75)
.padding(7)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(borderColor, lineWidth: 2))
.buttonStyle(.plain)
}
}
}
func scheduleNotifications() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { granted, error in
if let error = error {
print(error.localizedDescription)
}
}
center.removeAllPendingNotificationRequests()
for i in 1...10 {
let randomIndex = Int.random(in: 0..<messages.count)
let message = messages[randomIndex]
let content = UNMutableNotificationContent()
content.title = "Time to take a break!"
content.body = message
content.sound = UNNotificationSound.default
content.categoryIdentifier = "reminder"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(i * interval * 3600), repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
}
struct NotifyView_Previews: PreviewProvider {
static var previews: some View {
NotifyView()
}
}

Related

Why is it that every time I call fetchfollowingposts, the post user changes?

Any time the function "fetchfollowingposts" is called, the user's information in the feed cell changes. I've saved the post to firebase with the user's uid and i'm trying to fetch their profilephoto, fullname, etc. from the uid tied to the post. Any time I refresh the feedview, the user's information changes but the post itself never does (timestamp, post caption, post image, likes).
Since I don't fully understand the problem, I wasn't sure what files are needed so just let me know if i missed one. Thank you in advance for any help!
FeedCellView
import SwiftUI
import Kingfisher
struct FeedCell: View {
#ObservedObject var viewModel: FeedCellViewModel
#State private var isShowingBottomSheet = false
var didLike: Bool { return viewModel.post.didLike ?? false }
#Environment(\.presentationMode) var mode
init(viewModel: FeedCellViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack (alignment: .leading, spacing: 16) {
NavigationLink {
if let user = viewModel.post.user {
LazyView(ProfileView(user: user))
}
} label: {
HStack (alignment: .top) {
KFImage(URL(string: viewModel.post.user?.profileImageUrl ?? "https://firebasestorage.googleapis.com/v0/b/pageturner-951b4.appspot.com/o/profile_image%2FNoProfilePhoto.png?alt=media&token=1055648d-4d6e-4d51-b003-948a47b6bb76"))
.resizable()
.scaledToFill()
.frame(width: 48, height: 48)
.cornerRadius(10)
VStack (alignment: .leading, spacing: 4) {
Text(viewModel.post.user?.fullname ?? "")
.font(.system(size: 16))
.foregroundColor(Color(.label))
Text(viewModel.timestampString)
.font(.system(size: 14))
.foregroundColor(.gray)
}
Spacer()
if viewModel.post.isCurrentUser {
Button {
isShowingBottomSheet.toggle()
} label: {
Image(systemName: "ellipsis")
}.foregroundColor(Color(.gray))
.confirmationDialog("What do you want to do?",
isPresented: $isShowingBottomSheet) {
Button("Delete post", role: .destructive) {
viewModel.deletePost()
}
} message: {
Text("You cannot undo this action")
}
}
}
}
Text(viewModel.post.caption)
.font(.system(size: 16))
if let image = viewModel.post.imageUrl {
KFImage(URL(string: image))
.resizable()
.scaledToFill()
.frame(maxHeight: 250)
.cornerRadius(10)
}
HStack {
HStack (spacing: 24) {
Button {
didLike ? viewModel.unlike() : viewModel.like()
} label: {
Image(didLike ? "heart.fill" : "heart")
.renderingMode(.template)
.resizable()
.foregroundColor(didLike ? Color.accentColor : .black)
.frame(width: 24, height: 24)
Text("\(viewModel.post.likes)")
}
NavigationLink {
CommentView(post: viewModel.post)
} label: {
Image("comment")
.renderingMode(.template)
.resizable()
.frame(width: 24, height: 24)
Text("\(viewModel.post.stats?.CommentCount ?? 0)")
}
}
Spacer()
}
.foregroundColor(Color(.label))
}
}
}
FeedService
import SwiftUI
import FirebaseCore
import FirebaseAuth
import FirebaseFirestore
import FirebaseFirestoreSwift
struct FeedService {
func uploadPost(caption: String, image: UIImage?, completion: #escaping(Bool) -> Void) {
guard let uid = Auth.auth().currentUser?.uid else { return }
ImageUploader.uploadImage(image: image, type: .post) { imageUrl in
let data = ["uid": uid,
"caption": caption,
"likes": 0,
"imageUrl": imageUrl,
"timestamp": Timestamp(date: Date())] as [String: Any]
COLLECTION_POSTS.document()
.setData(data) { error in
if let error = error {
print("DEBUG: Failed to upload post with error: \(error.localizedDescription)")
completion(false)
return
}
}
completion(true)
}
}
func fetchFollowingPosts(forUid uid: String, completion: #escaping([Post]) -> Void) {
var posts = [Post]()
COLLECTION_FOLLOWING.document(uid).collection("user-following")
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
documents.forEach { doc in
let userId = doc.documentID
COLLECTION_POSTS.whereField("uid", isEqualTo: userId)
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
let post = documents.compactMap({ try? $0.data(as: Post.self) })
posts.append(contentsOf: post)
completion(posts.sorted(by: { $0.timestamp.dateValue() > $1.timestamp.dateValue()
}))
}
}
}
}
func uploadStory(caption: String?, image: UIImage, rating: Int?, completion: #escaping(Bool) -> Void) {
guard let uid = Auth.auth().currentUser?.uid else { return }
ImageUploader.uploadImage(image: image, type: .story) { imageUrl in
let data = ["uid": uid,
"caption": caption ?? "",
"imageUrl": imageUrl,
"rating": rating ?? "",
"isSeen": false,
"timestamp": Timestamp(date: Date())] as [String: Any]
COLLECTION_STORIES.document()
.setData(data) { error in
if let error = error {
print("DEBUG: Failed to upload story with error: \(error.localizedDescription)")
completion(false)
return
}
}
completion(true)
}
}
func fetchFollowingStories(forUid uid: String, completion: #escaping([Story]) -> Void) {
var stories = [Story]()
COLLECTION_FOLLOWING.document(uid).collection("user-following")
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
documents.forEach { doc in
let userId = doc.documentID
COLLECTION_STORIES.whereField("uid", isEqualTo: userId)
.getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
let story = documents.compactMap({ try? $0.data(as: Story.self) })
stories.append(contentsOf: story)
completion(stories.sorted(by: { $0.timestamp.dateValue() > $1.timestamp.dateValue()
}))
}
}
}
}
}
FeedViewModel
import SwiftUI
class FeedViewModel: ObservableObject {
#Published var followingPosts = [Post]()
#Published var followingStories = [Story]()
let service = FeedService()
let userService = UserService()
init() {
fetchFollowingPosts()
fetchFollowingStories()
}
func fetchFollowingPosts() {
guard let userid = AuthViewModel.shared.userSession?.uid else { return }
service.fetchFollowingPosts(forUid: userid) { posts in
self.followingPosts = posts
for i in 0 ..< posts.count {
let uid = posts[i].uid
self.userService.fetchUser(withUid: uid) { user in
self.followingPosts[i].user = user
}
}
}
}
func fetchFollowingStories() {
guard let userid = AuthViewModel.shared.userSession?.uid else { return }
service.fetchFollowingStories(forUid: userid) { stories in
self.followingStories = stories
for i in 0 ..< stories.count {
let uid = stories[i].uid
self.userService.fetchUser(withUid: uid) { user in
self.followingStories[i].user = user
}
}
}
}
}

Closing List view in SwiftUI

I have an app which reads a qr code, and then hits an api after using a key from the qr code, my problem now is, after it finds the data, I cannot figure out how to add a button which removes the layered list view to return to the root.. I have tried several solutions found here and on google and read through a lot of documentation, but none seemed to work..
import SwiftUI
import CodeScanner
extension URL {
var components: URLComponents? {
return URLComponents(url: self, resolvingAgainstBaseURL: false)
}
}
extension Array where Iterator.Element == URLQueryItem {
subscript(_ key: String) -> String? {
return first(where: { $0.name == key })?.value
}
}
struct Card: Decodable,Identifiable {
let id = UUID()
let sport: String
let year: String
let brand: String
let cardNumber: String
let playerName: String
let extra: String
let gradeName: String
let grade: String
let serial: String
let authDate: String
}
class apiCall {
func getUsers(apihit: String, completion:#escaping ([Card]) -> ()) {
guard let apihit = URL(string: apihit) else { return }
URLSession.shared.dataTask(with: apihit) { (data, _, _) in
let users = try! JSONDecoder().decode([Card].self, from: data!)
print(users)
DispatchQueue.main.async {
completion(users)
}
}
.resume()
}
}
struct ContentView: View {
#State var isPresentingScanner = false
#State var scannedCode: String = ""
#State var users: [Card] = []
var scannerSheet : some View {
CodeScannerView(
codeTypes: [.qr],
completion: { result in
if case let .success(code) = result {
self.scannedCode = code.string
self.isPresentingScanner = false
}
}
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray)
}
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}
func getDateFromTimeStamp(timeStamp : Double) -> String {
let date = NSDate(timeIntervalSince1970: timeStamp / 1000)
let dayTimePeriodFormatter = DateFormatter()
dayTimePeriodFormatter.dateFormat = "dd MMM YY, hh:mm a"
let dateString = dayTimePeriodFormatter.string(from: date as Date)
return dateString
}
var body: some View {
VStack(spacing: 10) {
Image("logo-white")
.offset(y: -200)
if let urlComponents = URL(string: scannedCode)?.components,
let cert = urlComponents.queryItems?["certificateNumber"] {
//Text(cert)
let apihit = URL(string: "https://app.example.com/api.php?apikey=xxxx&cert=\(cert)")!
NavigationView {
List(users) { user in
Text("Set: " + user.year + " " + user.brand)
.font(.headline)
if !user.extra.isEmpty {
Text("Desc: " + user.extra)
.font(.headline)
}
Text("Player: " + user.playerName)
.font(.headline)
Text("Year: " + user.year)
.font(.headline)
Text("Sport: " + user.sport)
.font(.headline)
Text("Grade Name: " + user.gradeName)
.font(.headline)
Text("Grade: " + user.grade)
.font(.headline)
Text("Card Serial: " + user.serial)
.font(.headline)
Text("Authenticated: " + user.authDate)
.font(.headline)
}
.onAppear {
apiCall().getUsers(apihit: apihit.absoluteString) { (users) in
self.users = users
}
}
.navigationTitle("Certificate Verification")
}
}
Button("Scan QR Code") {
self.isPresentingScanner = true
}
.padding()
.background(Color(red: 0, green: 0, blue: 0.5))
.foregroundColor(.white)
.clipShape(Rectangle())
.cornerRadius(20)
.sheet(isPresented: $isPresentingScanner) {
self.scannerSheet
Button("Close") {
self.isPresentingScanner = false
}
.padding()
.background(Color(red: 0, green: 0, blue: 0.5))
.foregroundColor(.white)
.clipShape(Rectangle())
.cornerRadius(20)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
an image of the view, which also doesn't stretch across the whole screen..
I resolved this by creating a new sheet for the list view, which now allows me to close it correctly.

Should I pass viewModel or only model to view to upload image/Pdf Document in swiftui in MVVM?Do I need to keep progress for each document?

I have a situation Where I can add multiple images or videos URLs in Array 1 by 1. Similarly, a separate View (AssetView) is modified based on the array elements added. Now, the status of the Image/Video/PDF upload is changed and needs to reflect the progress upload and progress done. But here, I am unable to use MVVM.
I am confused about where should I call the upload function.
Should I pass viewModel to asset View or only view??
I am adding the Source code below to show my problem.
This is the first Screen where the user will get an option to show the card. On Card, the User can select Image/Video/Pdf any document.
struct ContentView: View {
#State var cardShown = false
var body: some View {
NavigationView {
ZStack {
Button(action: {
cardShown.toggle()
}, label: {
Text("Show Card")
.bold()
.foregroundColor(Color.white)
.background(Color.blue)
.frame(width: 200, height: 50)
})
BottomCard(cardShown: $cardShown, height: 400, content: {
CardContent()
.padding()
})
}
}
}
}
This is the CardContentView, Where the user will add documents.
enum ActionType {
case ImageButtonAction
case VideoButtonAction
case None
}
struct CardContent: View {
#State private var text = ""
#State private var image: Image? = Image("UserProfilePlaceholder")
#State private var shouldPresentImagePicker = false
#State private var shouldPresentActionScheet = false
#State private var shouldPresentCamera = false
#State private var galleryAssetTypeSelected = GalleryAssetType.None
#State private var actionType = ActionType.None
#StateObject var messageAttachmentViewModel = MessageAttachmentViewModel()
// Document
#State private var shouldPresentDocumentPicker = false
var body: some View {
VStack {
Text("Photo Collage")
.bold()
.font(.system(size: 30))
.padding()
Text("You can create awesome photo grids and share them with all of your friends")
.font(.system(size: 18))
.multilineTextAlignment(.center)
TextEditor(text: $text)
.frame(height: 40)
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.messageAttachmentViewModel.commonMessageAttachmentModel.count, id: \.self) { i in
AssetView(messageAttachmentViewModel: messageAttachmentViewModel, index: i)
}
}
}
.background(Color.white)
.frame(height: 140)
HStack {
Button(action: {
self.shouldPresentActionScheet = true
self.actionType = .ImageButtonAction
}, label: {
Text("IMAGE")
})
Button(action: {
self.shouldPresentActionScheet = true
self.actionType = .VideoButtonAction
}, label: {
Text("VIDEO")
})
Button(action: {
self.galleryAssetTypeSelected = .PDF
self.shouldPresentDocumentPicker = true
}, label: {
Text("PDF")
})
Spacer()
Text("500")
.font(.system(size: 18))
Button(action: {
}, label: {
Text("SEND")
})
}
}
.padding()
.sheet(isPresented: $shouldPresentImagePicker) {
ImagePicker(sourceType: self.shouldPresentCamera ? .camera : .photoLibrary, image: self.$image, isPresented: self.$shouldPresentImagePicker, galleryAssetType: $galleryAssetTypeSelected, messageAttachmentViewModel: messageAttachmentViewModel)
}.actionSheet(isPresented: $shouldPresentActionScheet) { () -> ActionSheet in
ActionSheet(title: Text("Choose mode"), message: Text("Please choose your preferred mode to set your profile image"), buttons: [ActionSheet.Button.default(Text("Camera"), action: {
self.shouldPresentImagePicker = true
self.shouldPresentCamera = true
self.galleryAssetTypeSelected = .None
}), ActionSheet.Button.default(Text(actionType == ActionType.ImageButtonAction ? "Photo Library" : "Video"), action: {
self.shouldPresentImagePicker = true
self.shouldPresentCamera = false
self.galleryAssetTypeSelected = (actionType == ActionType.ImageButtonAction) ? GalleryAssetType.Photo : GalleryAssetType.Video
self.galleryAssetTypeSelected = actionType == ActionType.ImageButtonAction ? .Photo : .Video
}), ActionSheet.Button.cancel()])
}
// .sheet(isPresented: $shouldPresentDocumentPicker) {
// DocumentPicker(isDocumentPickerPresented: $shouldPresentDocumentPicker, galleryAssetType: $galleryAssetTypeSelected, commentAttachments: $commentAttachments)
// }
}
}
Below is Image Picker Struct to select Image/Video from Gallery.
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var image: Image?
#Binding var isPresented: Bool
#Binding var galleryAssetType: GalleryAssetType
#ObservedObject var messageAttachmentViewModel: MessageAttachmentViewModel
func makeCoordinator() -> ImagePickerViewCoordinator {
return ImagePickerViewCoordinator(image: $image, isPresented: $isPresented, galleryAssetType: $galleryAssetType, messageAttachmentViewModel: messageAttachmentViewModel)
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let pickerController = UIImagePickerController()
pickerController.sourceType = sourceType
pickerController.delegate = context.coordinator
if galleryAssetType == .Photo {
pickerController.mediaTypes = ["public.image"]
} else if galleryAssetType == .Video {
pickerController.mediaTypes = ["public.movie"]
pickerController.videoQuality = .typeHigh
}
return pickerController
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
// Nothing to update here
}
}
class ImagePickerViewCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var image: Image?
#Binding var isPresented: Bool
#Binding var galleryAssetType: GalleryAssetType
#ObservedObject var messageAttachmentViewModel: MessageAttachmentViewModel
init(image: Binding<Image?>, isPresented: Binding<Bool>, galleryAssetType: Binding<GalleryAssetType>, messageAttachmentViewModel: MessageAttachmentViewModel) {
self._image = image
self._isPresented = isPresented
self._galleryAssetType = galleryAssetType
self.messageAttachmentViewModel = messageAttachmentViewModel
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
self.image = Image(uiImage: image)
}
if galleryAssetType == .Photo {
if let imageURL = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerImageURL") ] as? URL {
let image = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerOriginalImage")] as? UIImage
let messageAttachmentModel = MessageAttachmentModel(assetType: .Photo, assetUrl: imageURL, image: image, uploadStatus: false)
self.messageAttachmentViewModel.commonMessageAttachmentModel.append(messageAttachmentModel)
}
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
self.image = Image(uiImage: image)
}
} else if galleryAssetType == .Video {
if let videoURL = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerMediaURL") ] as? URL {
let messageAttachmentModel = MessageAttachmentModel(assetType: .Video, assetUrl: videoURL, uploadStatus: false)
self.messageAttachmentViewModel.commonMessageAttachmentModel.append(messageAttachmentModel)
}
}
self.isPresented = false
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.isPresented = false
}
}
This is VideoThumnail View to show only thumbnail after selection. The actual video has to be uploaded to the server.
struct VideoThumbnail: View {
private enum LoadState {
case loading, success, failure
}
private class Loader: ObservableObject {
var videoThumbnail = UIImage()
var state = LoadState.loading
init(url: URL) {
if url.pathComponents.isEmpty {
self.state = .failure
return
}
let asset = AVAsset(url: url)
let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
avAssetImageGenerator.appliesPreferredTrackTransform = true
avAssetImageGenerator.maximumSize = CGSize(width: 150, height: 150)
let thumnailTime = CMTimeMake(value: 2, timescale: 1)
do {
let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumnailTime, actualTime: nil)
self.videoThumbnail = UIImage(cgImage: cgThumbImage)
self.state = .success
} catch {
print(error.localizedDescription)
self.state = .failure
}
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
#StateObject private var loader: Loader
var loading: Image
var failure: Image
var body: some View {
selectImage()
.resizable()
.aspectRatio(contentMode: .fit)
}
init(url: URL, loading: Image = Image(systemName: "photo"), failure: Image = Image(systemName: "multiply.circle")) {
_loader = StateObject(wrappedValue: Loader(url: url))
self.loading = loading
self.failure = failure
}
private func selectImage() -> Image {
switch loader.state {
case .loading:
return loading
case .failure:
return failure
default:
return Image(uiImage: loader.videoThumbnail)
}
}
}
Below is PDFThumbnail View.
struct PdfThumbnailView: View {
private enum LoadState {
case loading, success, failure
}
private class Loader: ObservableObject {
var pdfThumbnail = UIImage()
var state = LoadState.loading
init(url: URL) {
if url.pathComponents.isEmpty {
self.state = .failure
return
}
let pdfDocument = PDFDocument(url: url)
if let pdfDocumentPage = pdfDocument?.page(at: 1) {
pdfThumbnail = pdfDocumentPage.thumbnail(of: CGSize(width: 150, height: 150), for: PDFDisplayBox.trimBox)
self.state = .success
} else {
self.state = .failure
}
}
}
#StateObject private var loader: Loader
var loading: Image
var failure: Image
var body: some View {
selectImage()
.resizable()
.aspectRatio(contentMode: .fit)
}
init(url: URL, loading: Image = Image(systemName: "photo"), failure: Image = Image(systemName: "multiply.circle")) {
_loader = StateObject(wrappedValue: Loader(url: url))
self.loading = loading
self.failure = failure
}
private func selectImage() -> Image {
switch loader.state {
case .loading:
return loading
case .failure:
return failure
default:
return Image(uiImage: loader.pdfThumbnail)
}
}
}
MessageAttachmentModel: This Model is created when Image/Video/Pdf is selected.
struct MessageAttachmentModel {
var assetType = GalleryAssetType.None
var assetUrl: URL
var image: UIImage?
var uploadStatus: Bool
init(assetType: GalleryAssetType, assetUrl: URL, image: UIImage? = nil, uploadStatus: Bool) {
self.assetType = assetType
self.assetUrl = assetUrl
self.image = image
self.uploadStatus = uploadStatus
}
}
MessageAttachmentModelView: This ModelView contains an array of MessageAttachmentModel as a published property to reflect the change.
class MessageAttachmentViewModel: ObservableObject {
#Published var commonMessageAttachmentModel: [MessageAttachmentModel] = []
#Published var isUploadedLeft: Bool = false
func getIsUploadedStatus() {
let leftToUpload = commonMessageAttachmentModel.filter({ $0.uploadStatus == false })
isUploadedLeft = (leftToUpload.count > 0) ? true : false
}
func updateData() {
for var model in commonMessageAttachmentModel {
if model.uploadStatus == false {
if let endUsedId = getEndUserDataId(), let data = getDataFromURL(url: model.assetUrl) {
let timestamp = Date().timeIntervalSince1970
let key = "u_me_\(endUsedId)_\(timestamp))"
var assetType = (model.assetType == .Photo) ? ("Image") : ((model.assetType == .Video) ? "Video" : "Files")
uploadFileData(assetType: assetType, key: key, data: data) { status, urlString in
if status {
model.uploadStatus = true
}
}
}
}
}
}
func uploadFileData(assetType: String, key: String, data: Data , completion: #escaping (Bool, String) -> Void ) {
/// Server Data Upload
}
func getEndUserDataId() -> String? {
var endUserId: String?
return "5"
}
I have to show the progress of Image/Video/Pdf upload on Asset View. I am unable to identify how can I achieve it.
I am adding a simulator screenshot also to understand the situation clearly.
I am struggling to identify that do I need to keep progress for each document? Please help.

App passed the Instruments leak check but crashes after running for about 20 mins due to memory issues

I tested my app with Instruments and found no leaks but memory increases every time any value is updated and My app is updated every 0.5 second in multiple places. After running for about 20 mins, My app crashed and i got "Terminated due to memory issue" message.
I tried to find out which place cause this issue and ItemView seems to be causing the problem. I created a code snippet and the test result is below.
Please explain what's wrong with my code.
Thanks for any help!
import SwiftUI
import Combine
struct ContentView: View {
#State private var laserPower: Float = 0
var body: some View {
VStack {
ItemView(
title: "Laser Power",
value: $laserPower,
unit: "W",
callback: { (newValue) in
print("setPower")
//Api.setPower(watts: newValue)
}
)
}
.onAppear {
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
//Memory increases every time the laserPower's value is updated.
laserPower = Float(Int.random(in: 1..<160) * 10)
}
}
}
}
struct ItemView: View {
let title: String
#Binding var value: Float
let unit: String
let callback: (_ newValue: Float) -> Void
#State private var stringValue = ""
#State private var isEditingValue = false
#State var editingValue: Float = 0
func getStringValue(from value: Float) -> String {
return String(format: "%.1f", value)
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack {
Text(title)
Spacer()
}
HStack {
TextField(
"",
text: $stringValue,
onEditingChanged: { editingChanged in
DispatchQueue.main.async {
if !editingChanged {
if stringValue.contain(pattern: "^-?\\d+\\.\\d$") {
editingValue = Float(stringValue)!
} else if stringValue.contain(pattern: "^-?\\.\\d$") {
stringValue = "0.\(stringValue.split(separator: ".", omittingEmptySubsequences: false)[1])"
editingValue = Float(stringValue)!
} else if stringValue.contain(pattern: "^-?\\d+\\.?$") {
stringValue = "\(stringValue.split(separator: ".", omittingEmptySubsequences: false)[0]).0"
editingValue = Float(stringValue)!
} else {
stringValue = getStringValue(from: value)
}
callback(Float(getStringValue(from: editingValue))!)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isEditingValue = false
}
} else {
isEditingValue = true
}
}
},
onCommit: {
DispatchQueue.main.async {
callback(Float(getStringValue(from: editingValue))!)
}
}
)
.font(.title)
.multilineTextAlignment(.center)
.padding(4)
.overlay(RoundedRectangle(cornerRadius: 5).stroke())
Text(unit)
.padding(5)
}
}
.padding(10)
.onReceive(Just(stringValue)) { newValue in
if newValue.contain(pattern: "^-?\\d+\\.\\d?$") {
return
}
var filtered = newValue.filter { "0123456789.-".contains($0) }
let split = filtered.split(separator: ".", omittingEmptySubsequences: false)
if (split.count > 1 && String(split[1]).count > 1) || split.count > 2 {
let dec = split[1]
filtered = "\(split[0]).\(dec.isEmpty ? "" : String(dec[dec.startIndex]))"
}
self.stringValue = filtered
}
.onChange(of: value) { _ in
if !isEditingValue {
stringValue = getStringValue(from: self.value)
editingValue = value
}
}
.onAppear {
stringValue = getStringValue(from: value)
editingValue = value
}
}
}
extension String {
func contain(pattern: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options()) else {
return false
}
return regex.firstMatch(in: self, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, self.count)) != nil
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Memory report:
Instruments report:

Delete Local Notification if removed from Core Data SwiftUI

I made this simple code to try how Local Notifications works with Core Data and the main problem is that, after adding a Data Core item, I can receive my notification after 60 seconds but if I remove it I still receive it. Is there a function that I can call to delete that specific notification when I call my deleteItem function?
Another question that I have is how can I set a day of the week and timing to trigger that notification and not repeating after few seconds?
ContentView:
import UserNotifications
import SwiftUI
import CoreData
struct ContentView: View {
//Core Data
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Notifications.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Notifications.date, ascending: false)]) var notifications: FetchedResults<Notifications>
var titles = ["Hello", "Weekend", "Streaming", "ScoobyDoo"]
var subtitles = ["Hello2", "Weekend2", "Streaming2", "ScoobyDoo2"]
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: FavoriteView()) {
Text("Favorite View")
}
List {
ForEach(0 ..< titles.count) {title in
HStack {
Text(self.titles[title])
Image(systemName: "heart")
.onTapGesture {
if self.checkItem(title: self.titles[title]) {
do {
try self.deleteItem(title: self.titles[title])
print("title deleted")
} catch {
print(error)
}
} else {
self.addItem(item: self.titles[title])
print("item added")
// Notification content
let content = UNMutableNotificationContent()
content.title = self.titles[title]
content.subtitle = self.subtitles[title]
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
}
}
}
}
Button("Request Authorization") {
// Ask for notification when app launches
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set")
} else if let error = error {
print(error.localizedDescription)
}
}
}
Button("Remove Notifications") {
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
print("Removed")
}
}
}
}
private func checkItem(title: String) -> Bool {
let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Notifications")
request.predicate = NSPredicate(format: "title == %#", title)
request.fetchLimit = 1
var trueFalse = true
do {
let count = try managedObjectContext.count(for: request)
if count == 0 {
trueFalse = false
} else {
trueFalse = true
}
} catch {
print(error)
}
return trueFalse
}
private func deleteItem(title: String) throws {
let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Notifications")
request.predicate = NSPredicate(format: "title == %#", title)
try managedObjectContext.execute(NSBatchDeleteRequest(fetchRequest: request))
saveFavorites()
}
func addItem(item: String) {
let newItem = Notifications(context: managedObjectContext)
newItem.title = item
saveFavorites()
}
func saveFavorites() {
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
}
FavoriteView:
import SwiftUI
import CoreData
struct FavoriteView: View {
//Core Data
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Notifications.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Notifications.date, ascending: false)]) var notifications: FetchedResults<Notifications>
var body: some View {
List {
ForEach(notifications, id: \.self) { item in
Text(item.title)
}
}
}
}
Modify the core data model to include the notification identifier that you're providing while adding the request. And then while removing the notification from core data you can use this identifier to remove the local notification like this:
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])