1

Here is my DemoView:

struct ImageCropData: Identifiable {
    var id = UUID()
    var image: UIImage?
}

typealias CropImageHandler = (UIImage) async throws -> String

struct DemoView: View {
    @State private var data: ImageCropData?
    var body: some View {
        VStack {
            Button {
                data = ImageCropData(image: nil)
            } label: {
                Text("abc")
            }
        }
        .demo(data: $data) { image in
            print("do anything with image") // I need this block called everytime, no matter what ios system is in use
            return ""
        }
    }
}

extension View {
    func demo(data: Binding<ImageCropData?>, completion: @escaping CropImageHandler) -> some View {
        modifier(DemoModifier(croppingData: data, completion: completion))
    }
}

struct DemoModifier: ViewModifier {
    @Binding var croppingData: ImageCropData?
    var completion: CropImageHandler
    func body(content: Content) -> some View {
        if #available(iOS 17.0, *) {
            content
                .fullScreenCover(item: $croppingData) { data in
                    Text("Example View")
                }
        } else {
          //   content 
          //      .task {
          //          Task {
          //              print("different behavior")
          //              if let image = croppingData?.image {
          //                  _ = try await completion(image)
          //              }
          //          }
          //      }
          // What should I do here to return content and call completion immediately when iOS is 16.4 and lower.?
        }
    }
}


Simply when demo modifier is called I need two things:

  1. If iOS is 17 or more then display another View and care about completion inside. That is ok and works✅
  2. If iOS is 16.4 or less then return just that View and immediately call completion with given image. You might consider there always is an image. How can I achieve that?
6
  • Yes, I know... does it matter here?;) Commented Oct 3 at 8:06
  • It was just an example... When iOS is 17 there is a new View displayed as fullScreenCover and from within that view that block is called every time. For simplicity of that task I removed not necessary code. The more important things is else clause. Commented Oct 3 at 8:08
  • I know, but the issue is that the second part else is not called and not working directly. I can replace nil with any UIImage value. It will also not work. Commented Oct 3 at 8:09
  • @Sweeper I updated the question. It should be more clear now. Commented Oct 3 at 8:11
  • Okay, now it's more clear. The task only runs once per lifetime of the view. In this case it runs before you have set the croppingData to non-nil. Do you want it to run whenever croppingData changes to non-nil? Commented Oct 3 at 8:15

1 Answer 1

1

The reason why the completion handler is not running, is because the task is only run once, at the beginning of that view's lifecycle (think onAppear). At that time, croppingData is still nil.

You should use the overload of task that takes an id parameter changes.

content
    .task(id: croppingData?.id) {
        guard let croppingData else { return }
        if let image = croppingData.image {
            do {
                _ = try await completion(image)
            } catch {
                // handle error appropriately
            }
        }
    }

This task will run every time croppingData?.id changes. I used a guard at the beginning so that nothing happens when croppingData is nil. It is unnecessary to create a top-level Task { ... } for this, unless you want the task to not be cancelled even after the view's lifetime.

Sign up to request clarification or add additional context in comments.

1 Comment

Magic;) Thank you...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.