How to cancel process() in SwiftUI from different class - swiftui

I am running a binary file using a swift process() in my DataModel class. The user can see the process running from the ViewTasks() struct. How can I give the user the ability to cancel (terminate) the process?
Here is what I have tried:
DataModel class:
class DataModel : ObservableObject {
#Published var task = Process()
func RunTask() {
// add arguments to task here
do {
task.arguments = [args]
try task.run()
} catch {}
}
Here is the ViewTask() class:
struct ViewTask: View {
#ObservedObject var datamodel: DataModel
var body: some View {
///if user presses a cancel button, cancel the task running
datamodel.task.terminate()
}
This doesn't work because the task can't find the arguments. But when I don't make task an #Published variable, and it only exists locally in that Do loop, it works fine.
Maybe I need to declare/initialize it differently in the datamodel?
Thanks

Ok I figured out how to have a process (on background or main thread) visible through an observable object.
class DataModel : ObservableObject {
#Published var task = Process()
func RunTask() {
// add arguments to task here
do {
let temptask = Process()
temptask.arguments = [args]
// set observable object to this process
self.task = temptask
try task.run()
} catch {}
}
This will allow any other view to call datamodel.task.terminate() and it works just fine. If the process is in a background thread, simply use DispatchQueue.main.async{self.task = temptask} to set the observable task as the locally made process.

Related

SwiftUI: How wrong is to call the objectWillChange.send() after the change in the model data happened?

I'm trying to better understand the mechanism how SwiftUI gets notified about changes in observed data and invalidates UI.
In particular, I wonder if something won't work correctly (e.g. animations?) when objectWillChange.send() is called on ObservableObject after the underlying data model have changed, and more importantly why is that.
If it doesn't matter when the synthesized publisher notifies its observers after the change, then why is it named objectWillChange and not objectDidChange?
Let's say I have a view model like this the following and it observes changes in some model via a delegate method (the code is simplified for the example purposes):
protocol ModelDelegate: AnyObject {
func modelDidChange() {
}
class Model {
weak var delegate: ModelDelegate?
var stroredString: String {
didSet { self.delegate?.modelDidChange() }
}
}
class ViewModel: ObservableObject {
private let model: Model = Model(delegate: self)
var calculatedProperty: String { model.storedString }
}
extension ViewModel: ModelDelegate {
func modelDidChange() {
// we get called when the change has already happened.
// is it wrong to publish the change via objectWillChange?
self.objectWillChange.send()
}
}
And then the View looks like this:
struct MyView: View {
#ObservedObject private var viewModel: ViewModel
var body: some View {
Text(self.viewModel.calculatedProperty)
}
}
Question
Can I expect that something's not going to work correctly when objectWillChange.send() is called on ObservableObject after the underlying data model have changed? If yes, in which case and more importantly, why is that?
If it doesn't matter when the synthesized publisher notifies its observers after the change, then why is it named objectWillChange and not objectDidChange?

Couldn't cast SwiftUI.ExtensionDelegate to ExtensionDelegate

Working working with a SwiftUI Architecture WatchOS app, if you want to use ExtensionDelegate you need to create your own. I have done this, but when I try to actually access the delegate in the code, I am getting the following error message Could not cast value of type SwiftUI.ExtensionDelegate' (0x7fff8441b480) to 'TestMe_WatchKit_Extension.ExtensionDelegate' (0x10c3b36d0).
I have defined the ExtensionDelegate as -
class ExtensionDelegate: NSObject, WKExtensionDelegate {
var meetingStatistics: MeetingStatistics = MeetingStatistics()
override init(){
super.init()
}
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
print("applicationDidFinishLaunching for watchOS")
}
func applicationDidBecomeActive() {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillResignActive() {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, etc.
}
}
in my #main - I have the following:
#main
struct WatchApp: App {
#WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
// code
}
When I try to access the delegate as -
let delegate = WKExtension.shared().delegate as! ExtensionDelegate I get the above error.
The WKExtension.shared().delegate is not your ExtensionDelegate but internal SwiftUI.ExtensionDelegate (as stated), which provides your adapter delegate. You must use (pass everywhere) only instance of adapter delegate provided for your via
#WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
Update:
To use your delegate you have to pass it as argument next to view, eg as environment (so you can use it in any subview as #Environment(\.appDelegate) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.appDelegate, delegate)
}
}
and
struct DelegateKey: EnvironmentKey {
typealias Value = ExtensionDelegate?
static let defaultValue: ExtensionDelegate? = nil
}
extension EnvironmentValues {
var appDelegate: DelegateKey.Value {
get {
return self[DelegateKey.self]
}
set {
self[DelegateKey.self] = newValue
}
}
}
Well, - in the meantime I collected more experience. For my application delegate it is important that it is a singleton (only one instance exists). I realised that I have two.
#NSApplicationDelegateAdaptor(AppDelegate.self) var applicationDelegate
in the SwiftUI App struct initialises the first. To this App struct I added a init() method.
init() {
AppDelegate.shared = self.applicationDelegate
}
That initialises my AppDelegate static variable "shared" very early in the programme.
I removed all #Environment based access to the delegate, the key, the default value etc.. Global access is insured by
AppDelegate.shared
The issue with the self-defined #Environment variables is, that they can only be set with an .environment(_ keyPath:, _ value:) modifier on a view. However, my delegate is accessed before the first view sets the environment variable. Hence, my AppDelegate shared variable is now an ordinary static variable which I set myself as early as possible: in the init() method of the App struct.
I have the same problem in macOS. After declaring a class as the application delegate using in the #main App struct:
#NSApplicationDelegateAdaptor(AppDelegate.self) var applicationDelegate
The AppDelegate is correctly initialised when the Application launches.
I declare the environment key:
struct DelegateKey: EnvironmentKey {
typealias Value = AppDelegate
static let defaultValue: AppDelegate = AppDelegate()
}
extension EnvironmentValues {
var appDelegate: DelegateKey.Value {
get {
return self[DelegateKey.self]
}
set {
self[DelegateKey.self] = newValue
}
}
}
to access the variable via
#Environment(\.appDelegate) var appDelegate
That is fine for all SwiftUI environments.
The problem is that the general access to the application delegate via
NSApp.delegate
does not work any longer.
The solution is to declare a static variable in the AppDelegate class itself:
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
#Environment(\.appDelegate) static var shared
}
This makes the instantiated AppDelegate object accessible from everywhere via:
AppDelegate.shared
.

ObservedObject not triggering view redraw

I've a view which requires data obtaining from an api.
My understanding of SwiftUI is that by using onAppear the api will be called, viewmodel attribute updated, and the StateObject changed which will trigger a View redraw.
The issue is that no redraw is taking place.
I can see an api call occuring, and adding debug into after the decoded data is used I can see a lot of data is returned.
I've removed a lot of code to make the logic easier to follow (below).
Replacing #StateObject with #ObservedObject and passing into the view from a parent makes no difference either.
Thanks
struct FactoryDetailView: View {
var factory: Factory
#StateObject var factoryDetailsViewModel: FactoryDetailsViewModel()
var body: some View {
VStack {
Text(factory.name)
ForEach(factoryDetailsViewModel.details) { det in
Text(det)
}
}
.onAppear { factoryDetailsViewModel.loadDetails(factory) }
}
}
The viewmodel:
class FactoryDetailsViewModel: ApiViewModel {
#Published var details: [ String ]
func loadDetails(factory: Factory) {
// Do api call...
self.objectWillChange.send()
self.details = decodedResultsFromApiCall
self.objectWillChange.send()
}
class ApiViewModel: ObservableObject {
}
Well... removed details might be the reason of the issue, but in general approach should look like following
struct FactoryDetailView: View {
...
// assume it is a type and here there is initialization
#StateObject var factoryDetailsViewModel = FactoryDetailsViewModel()
...
now about self.objectWillChange.send() - don't call it, modifying published property it is called automatically
func loadDetails(factory: Factory) {
// Do api call...
{
// this is inside (!) API callback
DispatchQueue.main.async {
// update published property on main queue, always.
self.details = decodedResultsFromApiCall
}
}
}
This has the answer:
#Published property wrapper not working on subclass of ObservableObject
The issue I'm seeing is the same - subclassing an ObservableObject.
I've now got a solution working by '#Published var api_response' in the parent class and removing #Published from attributes in subclasses (although leaving '#Published' in them doesn't seem to cause any side effects so they may as well remain if only to document the intent).
Thanks for the responses.

Swift protocol delegate unexpectedly found nil in UIViewController

I have declared a protocol in one view controller and want to call a function in it's presenting view controller. I am not using storyboards.
This delegate!.setWishListButton() causes an error unexpectedly found nil while unwrapping an optional. I know I shouldn't use ! but it's for testing since my delegate function is never called.
I know there are many questions about this but none that matches my usage. I have a similar pattern in a UIView which works fine.
Here is my code
protocol SetWishListButtonDelegate {
func setWishListButton()
}
class MyViewController: UIViewController {
var delegate : SetWishListButtonDelegate?
------
func toggleWishListButton() {
delegate?.setWishListButton()
}
In another view controller
class anotherViewController: UIViewController, SetWishListButtonDelegate {
let myViewController = MyViewController()
override func viewDidLoad() {
super.viewDidLoad()
myViewController.delegate = self
}
func setWishListButton() {
print("DELEGATE METHOD FIRED")
wishListButton.isSelected = true
}
This worked after I removed a second declaration of my view controller in a function where it was presented by a button press.

Task chaining in JavaFX8: Start next Task after onSucceeded finished on previous task

I'm rather new to JavaFX8 and facing the following problem. In my current App, which is for document processing/editing, I have two rather expensive tasks. Opening a document and saving a document.
My app has the buttons "import next", "export current" and "export current and import next". For Import and Export, I have two Task of the following structure:
private class Export extends Task<Void> {
public Export() {
this.setOnRunning(event -> {
// do stuff (change cursor etc)
});
this.setOnFailed(event -> {
// do stuff, eg. show error box
});
this.setOnSucceeded(event -> {
// do stuff
});
}
#Override
protected Void call() throws Exception {
// do expensive stuff
return null;
}
}
I submit the task using the Executors.newSingleThreadExecutor();.
For the functionality "export current and import next", my goal is to submit the Export and Import tasks to the executor, but my Import tasks should only run if the export-task was sucessful and the EventHandler given in setOnSucceedded (whichs runs on the GUI thread) finished. If the export fails, it does not make any sense to load the next document because user interaction is needed. How can this be achieved?
First I tired to but the entire logic/error handling in the call method, but this does not work as I cannot change the GUI from this method (i.e. to show an error-box).
As workaround, I'm manually submitting the import-task on the last line of my setOnSucceeded in the export-task, but this is not very flexible, because I want to be sure this task exports only (without subsequent import)...
Don't call the handler property methods setOnXXX in your Task subclass constructor. These actually set a property on the task, so if you also call those methods from elsewhere you will replace the functionality you're implementing in the class itself, rather than add to it.
Instead, override the protected convenience methods:
public class Export extends Task<Void> {
#Override
protected void succeeded() {
super.succeeded();
// do stuff...
}
#Override
protected void running() {
super.running();
// do stuff...
}
#Override
protected void failed() {
super.failed();
// do stuff...
}
#Override
protected Void call() {
// do expensive stuff....
return null ;
}
}
Now you can safely use setOnXXX(...) externally to the Export class without breaking its functionality:
Export export = new Export();
export.setOnSucceeded(e -> {
Import import = new Import();
executor.submit(import);
});
executor.submit(export);
This puts the logic for chaining the tasks at the point where you actually create them, which would seem to be the correct place to do it.
Note that another way to provide multiple handlers for the change of state is to register listeners with the stateProperty():
Export export = new Export();
export.stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
// ...
}
});
From testing, it appears the order of execution of these different mechanisms is:
state listeners
the onSucceeded handler
the Task.succeeded method
All are executed on the FX Application Thread.
So if you want the code in the Task subclass to be executed before the handler added externally, do
public class Export extends Task<Void> {
public Export() {
stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.RUNNING) {
// do stuff
} else if (newState == Worker.State.SUCCEEDED) {
// do stuff
} else if (newState == Worker.State.FAILED) {
// do stuff
}
});
}
#Override
public Void call() {
// ...
}
}
Finally, you could implement the entire logic in your call method: if you need to interact with the UI you can wrap those calls in a Platform.runLater(() -> {});. However, separating the functionality into different tasks as you have done is probably cleaner anyway.