View Does Not Reload When Observed Object Changed - swiftui

I'm struggling with what I thought would be a pretty simple problem to solve.
See below...
I have a class - "Connect" - This is #Observable.
Within Connect I have a property "theMessage" which is #Published.
I then have my ContentView which references the property "theMessage"
When the app is launched the view loads OK but...
When the button is the methods within Connect are triggered but the view does not reload.
I believe that the problem is in the "receive" method towards the bottom of "Connect"
Within the closure of this method I can see I'm the debug..
incomingMessage received OK
theMethod gets set OK
But the view doesn't change
Any Help Or Ideas Would Be Appreciated
import Foundation
import Network
class Connect: ObservableObject {
static let sharedInstance = Connect()
private var talking: NWConnection?
private var listening: NWListener?
#Published var theMessage = "Still No Message"
// DEFINE LISTENER
func listenUDP(port: NWEndpoint.Port) {
do {
self.listening = try NWListener(using: .udp, on: port)
self.listening?.stateUpdateHandler = {(newState) in
switch newState {
case .ready:
print("ready")
default:
break
}
}
self.listening?.newConnectionHandler = {(newConnection) in
newConnection.stateUpdateHandler = {newState in
switch newState {
case .ready:
print("new connection")
self.receive(on: newConnection)
default:
break
}
}
newConnection.start(queue: DispatchQueue(label: "new client"))
}
} catch {
print("unable to create listener")
}
self.listening?.start(queue: .main)
}// END OF FUNC - LISTEN TO UDP
// DEFINE ON RECEIVE
func receive(on connection: NWConnection) {
connection.receiveMessage { (data, context, isComplete, error) in
if let error = error {
print(error)
return
}
if let data = data, !data.isEmpty {
let incomingString = String(decoding: data, as: UTF8.self)
print("Incoming String -\(incomingString)")
DispatchQueue.main.async { [weak self] in
self?.objectWillChange.send()
self?.theMessage = incomingString
print(self?.theMessage ?? "Self Got Binned")
}
}
}
}// END OF FUNC - RECEIVE
// DEFINE TALKER
func connectToUDP(hostUDP:NWEndpoint.Host,portUDP:NWEndpoint.Port) {
self.talking = NWConnection(host: hostUDP, port: portUDP, using: .udp)
self.talking?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
break
default:
break
}
}
self.talking?.start(queue: .main)
}// END OF DEFINE TALKER
// SEND A MESSAGE
func sendUDP(_ content: String) {
let contentToSendUDP = content.data(using: String.Encoding.utf8)
self.talking?.send(content: contentToSendUDP, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
// code
} else {
print("ERROR! Error when data (Type: String) sending. NWError: \n \(NWError!) ")
}
})))
}
}// END OF CLASS - CONNECT
import SwiftUI
import Network
struct ContentView: View {
#ObservedObject var connect = Connect.sharedInstance
let communication = Connect()
var body: some View {
VStack {
Text("Incoming Message - \(self.connect.theMessage)")
.padding(100)
.onAppear(){
// LISTENER
let port2U = NWEndpoint.Port.init(integerLiteral: 1984)
communication.listenUDP(port: port2U)
}
Button(action: {
let host = NWEndpoint.Host.init("localhost")
let port = NWEndpoint.Port.init("1984")
self.communication.connectToUDP(hostUDP: host, portUDP: port!)
self.communication.sendUDP("/cue/MyText/start")
}) {
Text("smoke")
}
}// END VSTACK
}// END OF BODY
}// END OF VIEW

This code in your Connect class creates a singleton instance
static let sharedInstance = Connect()
Then the View creates another instance with this
let communication = Connect()
One cannot see what the other is doing. It is like creating having 2 cars, 2 houses, 2 people.
Remove communication and replace with connect. Observe and use the Singleton.
struct ConnectView: View {
#ObservedObject var connect = Connect.sharedInstance
var body: some View {
VStack {
Text("Incoming Message - \(self.connect.theMessage)")
.padding(100)
.onAppear(){
// LISTENER
let port2U = NWEndpoint.Port.init(integerLiteral: 1984)
connect.listenUDP(port: port2U)
}
Button(action: {
let host = NWEndpoint.Host.init("localhost")
let port = NWEndpoint.Port.init("1984")
self.connect.connectToUDP(hostUDP: host, portUDP: port!)
self.connect.sendUDP("/cue/MyText/start")
}) {
Text("smoke")
}
}// END VSTACK
}// END OF BODY
}// END OF VIEW
It is good practice to make the initializer of a class private if you will have a Singleton pattern.
Add this to your Connect class to prevent this issue.
private init(){
}

Related

Displaying and Logging Persistence and App Store Errors

I am trying to figure out how to display errors such as those found in the App Store Store class and Persistence.swift file in a single reporting module. Some of my errors within views use an environment object to tie the error production to the error reporting, but this isn’t feasible with errors in different classes.
So any pointers on handling these types of errors would be much appreciated.
Below is an example of my process to report an error within a view
// the save button has been pressed
struct saveButton: View {
#Environment(\.managedObjectContext) var viewContext
#EnvironmentObject var errorHandling: ErrorHandling
var body: some View {
// prepping data for save to core data
do {
try viewContext.save()
} catch {
// report: Unable to Save Transaction
self.errorHandling.handle(error: AppError.saveTransactionError)
}
The App Store purchase method (below) may produce two errors that I would like to display with my app. This is the type of logic that I need help display the error within a view
#MainActor
func purchase() {
Task.init {
guard let product = products.first else {
return
}
guard AppStore.canMakePayments else {
return
}
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
await transaction.finish()
// save to user defaults
self.purchasedIds = transaction.productID
break
case .unverified:
// throw PurchaseError.failed
// FIX: report error (Unable to purchase verification failed)
break
}
break
case .userCancelled:
break
// asked to buy (setting on phone prevents purchases)
case .pending:
break
default:
break
}
}
catch {
print(error.localizedDescription)
// FIX: report error (Unable to Complete Purchase)
}
}
}
enum AppError: LocalizedError {
case storePurchaseError
case storeVerificationError
var errorDescription: String? {
switch self {
case .storePurchaseError:
return NSLocalizedString("Store Purchase Error", comment: "")
case .storeVerificationError:
return NSLocalizedString("Store Verification Error", comment: "")
}
}
}
Below is some code I have been using to display an alert
struct ErrorAlert: Identifiable {
var id = UUID()
var message: String
var dismissAction: (() -> Void)?
}
class ErrorHandling: ObservableObject {
#Published var currentAlert: ErrorAlert?
func handle(error: Error) {
currentAlert = ErrorAlert(message: error.localizedDescription)
}
}
struct HandleErrorsByShowingAlertViewModifier: ViewModifier {
#StateObject var errorHandling = ErrorHandling()
func body(content: Content) -> some View {
content
.environmentObject(errorHandling)
.background(
EmptyView()
.alert(item: $errorHandling.currentAlert) { currentAlert in
Alert(
title: Text("Error"),
message: Text(currentAlert.message),
dismissButton: .default(Text("Ok")) {
currentAlert.dismissAction?()
}
)
}
)
}
}
extension View {
func withErrorHandling() -> some View {
modifier(HandleErrorsByShowingAlertViewModifier())
}
}

Memory Leak with swiftUI List and Firebase database observing childAdded

I'm writing a chat application using firebase , I notice memory leak in ChatView while observing database changes which is when a message is sent or received.
when I comment out the database observation the memory leak dose not happen anymore so I'm guessing this is a firebase problem .
I'm sharing the code so please if you know what is acutely causing the memory leak help me out.
ChatViewModel:
class ChatViewModel : ObservableObject {
/// - sub ViewModels :
#Published private(set) var messages : [MessageModel] = []
private(set) var conversationID : String? = nil
/// set shared conversationID
/// - Parameter convesationID: shared conversationID if exist
func setConverationID(convesationID : String?){
guard let convesationID = convesationID else {
print("CONVERSATION ID DOUS NOT EXIT")
return
}
self.conversationID = convesationID
startObservingConversation()
}
/// start observing the conversation with viewModel conversationID
private func startObservingConversation(){
guard let conversationID = self.conversationID else {
return
}
DatabaseManager.shared.observeMessagesForConversation(conversationId: conversationID) { [weak self] message in
self?.messages += message
}
}}
ChatView :
struct ChatView: View {
#StateObject var viewModel = ChatViewModel()
var body: some View {
VStack(alignment : .leading , spacing: 0){
ScrollViewReader { scrollViewReader in
List{
ForEach(viewModel.messages) { item in
MessageView(messsage: item.text)
.id(item.id)
}
}
}
}
}}
observerMessages :
func observeMessagesForConversation(conversationId id :String,compelition : #escaping ([MessageModel]) -> Void ) {
database.child(id).child("messages").observe(.childAdded) { snapshot in
guard let value = snapshot.value as? [String:Any] else {
compelition([])
return
}
var messages : [MessageModel] = []
let decoder = JSONDecoder()
guard
let jsonData = try? JSONSerialization.data(withJSONObject:value),
let message = try? decoder.decode(MessageModel.self, from: jsonData) else {
compelition([])
return
}
messages.append(message)
compelition(messages)
}
}

StoreKit problem while restoring In-App Purchase

I tried to find a tutorial about IAP, and I found : This one
But when I call function to restore, nothing happens. OnTap button I call : store.restorePurchase()
extension Store {
func product(for identifier: String) -> SKProduct? { ...}
func purchaseProduct (_ product: SKProduct){ ...}
func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
print("restore")
}
}
restore is print in my console. It is the only thing that is printed in console. But in the code above, you can see .restored:that should print "UserIsPremium"
extension Store : SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions : [SKPaymentTransaction]){
for transaction in transactions {
var shouldFinishTransaction = false
switch transaction.transactionState{
case .purchased, .restored:
completedPurchases.append(transaction.payment.productIdentifier)
print("UserIsPremium")
shouldFinishTransaction = true
case .failed:
print("NotPremium")
shouldFinishTransaction = true
case .deferred, .purchasing:
print("...")
break
#unknown default:
print("unknown")
break
}
if shouldFinishTransaction {
print("shouldfinish")
SKPaymentQueue.default().finishTransaction(transaction)
DispatchQueue.main.async {
self.purchaseCompletionHandler?(transaction)
self.purchaseCompletionHandler = nil
}
}
}
}
}
My class Store :
import StoreKit
typealias FetchCompletionHandler = (([SKProduct]) -> Void)
typealias PurchaseCompletionHandler = ((SKPaymentTransaction?) -> Void)
class Store: NSObject, ObservableObject {
#Published var allFullVersion = [FullVersion]()
private let allProductsIdentifiers = Set ([
"ClemPapplis.OrientationEPS.versionFull"
])
private var completedPurchases = [String]() {
didSet {
DispatchQueue.main.async{ [weak self] in
guard let self = self else { return }
for index in self.allFullVersion.indices {
if self.completedPurchases.contains(self.allFullVersion[index].id){
print("completedPurchases")
UserDefaults.standard.setValue(true, forKey: "Premium")
}
self.allFullVersion[index].isLocked =
!self.completedPurchases.contains(self.allFullVersion[index].id)
}
}
}
}
I use sandbox testers.
If I buy item, no problem, datas are print in console.
Did I forget something ?
It's been a few years since I used StoreKit, but I remember having an issue with restore purchases also (and yes, #ElTomato is correct, use a separate case - I'm surprised your code actually builds). I found that the following worked:
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
delegate.purchaseComplete()
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
default:
break
}}}
}
func restorePurchases() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
for _ in queue.transactions {
alreadyPurchased = true
delegate.purchasesRestored()
}
showAlert("Purchases have been restored.")
}
Don't worry about showAlert, that's just an extension I have for UIViewController. But what I'm not seeing in your code is paymentQueueRestoreCompletedTransactionsFinished(_ queue:).
I followed the same tutorial i think you did not select the select the none in here
configuration
to restore in your simulator you should select none when you select none it tries to connect apple developers in app purchase configuration. give it a try and let me know.

Use the ORSSerialPort Library with Combine and SwiftUi. Problem by trying to subscribe a Key-Value Observing(KVO) object

I am trying to integrate the ORSSerialPort framework with Combine into SwiftUi. ORSSerialPort GitHub
ORSSerialPort implements Key-Value Observing(KVO) and a Delegate Pattern. In his example, he works with the Delegate Pattern and UIKit. I will use Combine and SwiftUI. Not the Delegate Pattern.
Because the ORSSerialPort is a KVO Object, it should be possible ORSSerialPort variables to subscribe, but her is my Problem. It will not work. I don't know why.
what i try:
#Published var isOpen: String = ""
var orsSerialPort: ORSSerialPort = ORSSerialPort(path: "/dev/cu.usbmodem146101")!
var publisher: NSObject.KeyValueObservingPublisher<ORSSerialPort, Bool>
var isOpenSub: AnyCancellable?
init(){
publisher = orsSerialPort.publisher(for: \.isOpen)
isOpenSub = publisher
.map{ (x) -> String in return "Is \(x ? "Opend":"Close" )" }
.assign(to: \.isOpen, on: self)
}
The publisher is trigger always one time, and that is by the init. If I close and open the port with my function, it will not trigger again.
func cloes(){
orsSerialPort.close()
}
func open(){
orsSerialPort.open()
}
For Debugging I try this to the the Stream. And I will see again that the Publisher will always trigger only one time. But the Port is closing and opining.
.sink(receiveCompletion: { completion in
print("subScribeIsOpen complet")
switch completion {
case .finished:
print("subScribeIsOpen complet")
case .failure(let error):
print("subScribeIsOpen fail")
print(error.localizedDescription)
}
}, receiveValue: { value in
print("subScribeIsOpen receive \(value)")
self.isOpen = value
})
My Demo Code
import Foundation
import ORSSerial
import Combine
import SwiftUI
class ORSSerialPortCombine: ObservableObject {
#Published var status: String = ""
#Published var isOpen: String = ""
var orsSerialPort: ORSSerialPort = ORSSerialPort(path: "/dev/cu.usbmodem146101")!
var publisher: NSObject.KeyValueObservingPublisher<ORSSerialPort, Bool>
var isOpenSub: AnyCancellable?
init(){
publisher = orsSerialPort.publisher(for: \.isOpen)
isOpenSub = publisher
.map{ (x) -> String in return "Is \(x ? "Opend":"Close" )" }
// .assign(to: \.isOpen, on: self)
.sink(receiveCompletion: { completion in
print("subScribeIsOpen complet")
switch completion {
case .finished:
print("subScribeIsOpen complet")
case .failure(let error):
print("subScribeIsOpen fail")
print(error.localizedDescription)
}
}, receiveValue: { value in
print("subScribeIsOpen receive \(value)")
self.isOpen = value
})
}
func cloes(){
orsSerialPort.close()
}
func open(){
orsSerialPort.open()
}
func updateStatus(){
status = "Serial port is \(orsSerialPort.isOpen ? "Opend":"Close" )"
}
}
struct ORSSerialPortCombineView: View{
#ObservedObject var model = ORSSerialPortCombine()
var body: some View{
VStack{
Text( model.isOpen )
HStack{
Button("Close") { model.cloes() }
Button("Open") { model.open() }
Button("Staus") { model.updateStatus() }
}
Text( model.status )
}
}
}

How do you use the result of a function as a Bindable object in Swiftui?

I'm developing a simple SwiftUI app, using Xcode 11 beta5.
I have a list of Place, and i want to display the list, and add / edit them.
The data come from core data.
I have 3 classes for this :
- CoreDataController, which handle the connection to core data
- PlaceController, which handle operation on the Places.
public class CoreDataController {
static let instance = CoreDataController()
private let container = NSPersistentContainer(name: "RememberV2")
private init() {
print("Start Init DataController")
container.loadPersistentStores { (storeDescription, error) in
if let error = error {
fatalError("Failed to load store: \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
print("End Init DataController")
}
func getContext() -> NSManagedObjectContext {
return container.viewContext
}
func save() {
print("Start Save context")
do{
try container.viewContext.save()
} catch {
print("ERROR - saving context")
}
print("End Save context")
}
}
public class PlaceController {
static let instance = PlaceController()
private let dc = CoreDataController.instance
private let entityName:String = "Place"
private init() {
print("Start init Place Controller")
print("End init Place Controller")
}
func createPlace(name:String) -> Bool {
let newPlace = NSEntityDescription.insertNewObject(forEntityName: entityName, into: dc.getContext())
newPlace.setValue(UUID(), forKey: "id")
newPlace.setValue(name, forKey: "name")
dc.save()
DataController.instance.places = getAllPlaces()
return true
}
func createPlace(name:String, comment:String) -> Bool {
print("Start - create place with comment")
let newPlace = NSEntityDescription.insertNewObject(forEntityName: entityName, into: dc.getContext())
newPlace.setValue(UUID(), forKey: "id")
newPlace.setValue(name, forKey: "name")
newPlace.setValue(comment, forKey: "comment")
dc.save()
print("End - create place with comment")
DataController.instance.places = getAllPlaces()
return true
}
func getAllPlaces() -> [Place] {
let r = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
if let fp = try? dc.getContext().fetch(r) as? [Place] {
return fp
}
return [Place]()
}
func truncatePlaces() -> Bool {
let r = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
let batch = NSBatchDeleteRequest(fetchRequest: r)
if (try? dc.getContext().execute(batch)) != nil {
return true
}
return false
}
}
In my view i simply use the function :
List (pc.getAllPlaces(), id: \.id) { place in
NavigationLink(destination: PlaceDetail(place: place)) {
PlacesRow(place:place)
}
}
It works to display the information, but if i add a new place, the list is not updated.
I have to go back to the home screen, then display again the Places screen for the list to be updated.
So i use another controller :
class DataController: ObservableObject {
#Published var places:[Place] = []
static let instance = DataController()
private init() {
print("Start init Place Controller")
print("End init Place Controller")
}
}
In my view, i just display the ObservedObject places.
#ObservedObject var data: DataController = DataController.instance
And in my PlaceController, i update the table in the DataController
DataController.instance.places = getAllPlaces()
That works, but i have this warning :
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes
Also i'm pretty sure there is a better way to do this ...
Any idea what is this better way ?
Thanks,
Nicolas