SwiftUI - Memory Management - memory-management

OK, so I'm looking to improve memory within my app.
I have enabled Live Memory Allocation for my project and I'm using the Debug Graph Tool. I'm looking at the backtraces, and coming across issues, which in total honesty, does not make sense to me. I've, to the best of my knowledge, removed strong references, but I am getting issues with parts of my code where I just don't understand/see the issue. And example of this is:
struct ProductScrollView: UIViewRepresentable {
private let view = UIScrollView()
func makeCoordinator() -> Coordinator {
return ProductScrollView.Coordinator(parent1: self)
}
#State var currentPage: Int
#Binding var products: [ProductModel]
func makeUIView(context: Context) -> UIScrollView {
if view.superview == .none {
let childView = UIHostingController(rootView: ProductCell(products: $products, currentPage: currentPage, pageCount: products.count)
) <---- Debugger seems to indicate this with the message "Thread 1"
childView.view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
view.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
view.addSubview(childView.view)
view.showsVerticalScrollIndicator = false
view.showsHorizontalScrollIndicator = false
view.contentInsetAdjustmentBehavior = .never
view.isPagingEnabled = true
view.delegate = context.coordinator
}
return view
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
uiView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
for i in 0..<uiView.subviews.count {
uiView.subviews[i].frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
}
}
class Coordinator: NSObject, UIScrollViewDelegate {
var parent: ProductScrollView
init(parent1: ProductScrollView){
parent = parent1
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(scrollView.contentOffset.y / UIScreen.main.bounds.height)
parent.currentPage = index
}
deinit {
print("Coordiante scroll view desroted")
}
}
}
Which doesn't make complete sense to me, however, I believe the line:
return ProductScrollView.Coordinator(parent1: self)
May be the issue?
I'll even have the debugger point to functions that are performed on my onAppear method.
Could someone please help as to what I can do to better understand these issues and eliminate them?
Edit -
To delve even further, this line appears in my backtrace:
static var FEEDBACK = FeedbackAPI()
Is it due to creating a new instance everytime?

By design we need to instantiate view inside makeView, so SwiftUI manages UIView lift-cycle to be the same as representable view, like
struct ProductScrollView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
return ProductScrollView.Coordinator(parent1: self)
}
#State var currentPage: Int
#Binding var products: [ProductModel]
func makeUIView(context: Context) -> UIScrollView {
let view = UIScrollView() // << here !!
// ... other code
}

Related

How can I disable auto-zooming when using userTrackingMode?

I am trying to zoom out of a map but it keeps zooming back in because of the .userTrackingMode that is set to .follow.
Is there a way to prevent or to write your own tracking mode method?
I tried to disable the tracking for 20seconds or so, but I ended up with the same problem. I also tried to just use the camera by sending it of to the users location but that did not work either.
Here is my code:
import SwiftUI
import Combine
import MapKit
struct MyMapView: UIViewRepresentable {
#State var locationManager = CLLocationManager()
#Binding var span: Double
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MyMapView
init(_ parent: MyMapView) {
self.parent = parent
super.init()
}
}
func makeUIView(context: Context) -> MKMapView {
setupManager()
let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.mapType = MKMapType.satellite
mapView.showsUserLocation = true
mapView.showsCompass = false
mapView.isZoomEnabled = true
mapView.delegate = context.coordinator
// Version 1
mapView.userTrackingMode = .follow
/*
// Version 2
let camera1 = MKMapCamera(
lookingAtCenter: mapView.centerCoordinate,
fromEyeCoordinate: mapView.centerCoordinate,
eyeAltitude: span)
mapView.setCamera(camera1, animated: false)
*/
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// Version 1 -- zoom out of the map over the span variable
uiView.setRegion(MKCoordinateRegion(center: uiView.centerCoordinate, latitudinalMeters: CLLocationDistance(span), longitudinalMeters: CLLocationDistance(span)), animated: false)
// Deactivating the tracking and activating the tracking after 60sec and then disable it again.
uiView.userTrackingMode = .none
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) {
uiView.userTrackingMode = .follow
}
uiView.userTrackingMode = .none
/*
// Version 2 -- Camera
let camera2 = MKMapCamera(
lookingAtCenter: uiView.centerCoordinate,
fromEyeCoordinate: uiView.centerCoordinate,
eyeAltitude: span)
uiView.setCamera(camera2, animated: false)
*/
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func setupManager() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
}
Is there a solution for my problem?

View Modifier messing with animations

I've been looking to add a loading indicator to my project, and found a really cool animation here. To make it easier to use, I wanted to incorporate it into a view modifier to put it on top of the current view. However, when I do so, it doesn't animate when I first press the button. I have played around with it a little, and my hypothesis is that the View Modifier doesn't pass the initial isAnimating = false, so only passes it isAnimating = true when the button is pressed. Because the ArcsAnimationView doesn't get the false value initially, it doesn't actually animate anything and just shows the static arcs. However, if I press the button a second time afterwards, it seems to be initialized and the view properly animates as desired.
Is there a better way to structure my code to avoid this issue? Am I missing something key? Any help is greatly appreciated.
Below is the complete code:
import SwiftUI
struct ArcsAnimationView: View {
#Binding var isAnimating: Bool
let count: UInt = 4
let width: CGFloat = 5
let spacing: CGFloat = 2
init(isAnimating: Binding<Bool>) {
self._isAnimating = isAnimating
}
var body: some View {
GeometryReader { geometry in
ForEach(0..<Int(count)) { index in
item(forIndex: index, in: geometry.size)
// the rotation below is what is animated ...
// I think the problem is that it just starts at .degrees(360), instead of
// .degrees(0) as expected, where it is then animated to .degrees(360)
.rotationEffect(isAnimating ? .degrees(360) : .degrees(0))
.animation(
Animation.default
.speed(Double.random(in: 0.05...0.25))
.repeatCount(isAnimating ? .max : 1, autoreverses: false)
, value: isAnimating
)
.foregroundColor(Color(hex: AppColors.darkBlue1.rawValue))
}
}
.aspectRatio(contentMode: .fit)
}
private func item(forIndex index: Int, in geometrySize: CGSize) -> some View {
Group { () -> Path in
var p = Path()
p.addArc(center: CGPoint(x: geometrySize.width/2, y: geometrySize.height/2),
radius: geometrySize.width/2 - width/2 - CGFloat(index) * (width + spacing),
startAngle: .degrees(0),
endAngle: .degrees(Double(Int.random(in: 120...300))),
clockwise: true)
return p.strokedPath(.init(lineWidth: width))
}
.frame(width: geometrySize.width, height: geometrySize.height)
}
}
struct ArcsAnimationModifier: ViewModifier {
#Binding var isAnimating: Bool
func body(content: Content) -> some View {
ZStack {
if isAnimating {
ArcsAnimationView(isAnimating: _isAnimating)
.frame(width: 150)
}
content
.disabled(isAnimating)
}
}
}
extension View {
func loadingAnimation(isAnimating: Binding<Bool>) -> some View {
self.modifier(ArcsAnimationModifier(isAnimating: isAnimating))
}
}
Here is where I actually call the function:
struct AnimationView: View {
#State var isAnimating = false
var body: some View {
VStack {
Button(action: {
self.isAnimating = true
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
self.isAnimating = false
}
}, label: {
Text("show animation")
})
}
.loadingAnimation(isAnimating: $isAnimating)
}
}
Note: I am fairly certain the issue is with View Modifier since if I call ArcsAnimationView as a regular view in AnimationView, it works as expected.
I get there to see some implementation, but I think others would prefer a simple base to start from.
here my 2 cents to show how to write an AnimatableModifier that can be used on multiple objects cleaning up ".animation" in code.
struct ContentView: View {
#State private var hideWhilelUpdating = false
var body: some View {
Image(systemName: "tshirt.fill")
.modifier(SmoothHideAndShow(hide: hideWhilelUpdating))
Text("Some contents to show...")
.modifier(SmoothHideAndShow(hide: hideWhilelUpdating))
Button( "hide and show smootly") {
hideWhilelUpdating.toggle()
}
.padding(60)
}
}
struct SmoothHideAndShow: AnimatableModifier {
var hide: Bool
var animatableData: CGFloat {
get { CGFloat(hide ? 0 : 1) }
set { hide = newValue == 0 }
}
func body(content: Content) -> some View {
content
.opacity(hide ? 0.2 : 1)
.animation(.easeIn(duration: 1), value: hide)
}
}
when pressing button, our bool will trigger animation that fades in and out our text.
I use it during network calls (omitted for clarity... and replaced with button) to hide values under remote update. When network returns, I toggle boolean.

Is there any way to determine the end of animation? [duplicate]

I have a swiftUI animation based on some state:
withAnimation(.linear(duration: 0.1)) {
self.someState = newState
}
Is there any callback which is triggered when the above animation completes?
If there are any suggestions on how to accomplish an animation with a completion block in SwiftUI which are not withAnimation, I'm open to those as well.
I would like to know when the animation completes so I can do something else, for the purpose of this example, I just want to print to console when the animation completes.
Unfortunately there's no good solution to this problem (yet).
However, if you can specify the duration of an Animation, you can use DispatchQueue.main.asyncAfter to trigger an action exactly when the animation finishes:
withAnimation(.linear(duration: 0.1)) {
self.someState = newState
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
print("Animation finished")
}
Here's a bit simplified and generalized version that could be used for any single value animations. This is based on some other examples I was able to find on the internet while waiting for Apple to provide a more convenient way:
struct AnimatableModifierDouble: AnimatableModifier {
var targetValue: Double
// SwiftUI gradually varies it from old value to the new value
var animatableData: Double {
didSet {
checkIfFinished()
}
}
var completion: () -> ()
// Re-created every time the control argument changes
init(bindedValue: Double, completion: #escaping () -> ()) {
self.completion = completion
// Set animatableData to the new value. But SwiftUI again directly
// and gradually varies the value while the body
// is being called to animate. Following line serves the purpose of
// associating the extenal argument with the animatableData.
self.animatableData = bindedValue
targetValue = bindedValue
}
func checkIfFinished() -> () {
//print("Current value: \(animatableData)")
if (animatableData == targetValue) {
//if animatableData.isEqual(to: targetValue) {
DispatchQueue.main.async {
self.completion()
}
}
}
// Called after each gradual change in animatableData to allow the
// modifier to animate
func body(content: Content) -> some View {
// content is the view on which .modifier is applied
content
// We don't want the system also to
// implicitly animate default system animatons it each time we set it. It will also cancel
// out other implicit animations now present on the content.
.animation(nil)
}
}
And here's an example on how to use it with text opacity animation:
import SwiftUI
struct ContentView: View {
// Need to create state property
#State var textOpacity: Double = 0.0
var body: some View {
VStack {
Text("Hello world!")
.font(.largeTitle)
// Pass generic animatable modifier for animating double values
.modifier(AnimatableModifierDouble(bindedValue: textOpacity) {
// Finished, hurray!
print("finished")
// Reset opacity so that you could tap the button and animate again
self.textOpacity = 0.0
}).opacity(textOpacity) // bind text opacity to your state property
Button(action: {
withAnimation(.easeInOut(duration: 1.0)) {
self.textOpacity = 1.0 // Change your state property and trigger animation to start
}
}) {
Text("Animate")
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
On this blog this Guy Javier describes how to use GeometryEffect in order to have animation feedback, in his example he detects when the animation is at 50% so he can flip the view and make it looks like the view has 2 sides
here is the link to the full article with a lot of explanations: https://swiftui-lab.com/swiftui-animations-part2/
I will copy the relevant snippets here so the answer can still be relevant even if the link is not valid no more:
In this example #Binding var flipped: Bool becomes true when the angle is between 90 and 270 and then false.
struct FlipEffect: GeometryEffect {
var animatableData: Double {
get { angle }
set { angle = newValue }
}
#Binding var flipped: Bool
var angle: Double
let axis: (x: CGFloat, y: CGFloat)
func effectValue(size: CGSize) -> ProjectionTransform {
// We schedule the change to be done after the view has finished drawing,
// otherwise, we would receive a runtime error, indicating we are changing
// the state while the view is being drawn.
DispatchQueue.main.async {
self.flipped = self.angle >= 90 && self.angle < 270
}
let a = CGFloat(Angle(degrees: angle).radians)
var transform3d = CATransform3DIdentity;
transform3d.m34 = -1/max(size.width, size.height)
transform3d = CATransform3DRotate(transform3d, a, axis.x, axis.y, 0)
transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0)
let affineTransform = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0))
return ProjectionTransform(transform3d).concatenating(affineTransform)
}
}
You should be able to change the animation to whatever you want to achieve and then get the binding to change the state of the parent once it is done.
You need to use a custom modifier.
I have done an example to animate the offset in the X-axis with a completion block.
struct OffsetXEffectModifier: AnimatableModifier {
var initialOffsetX: CGFloat
var offsetX: CGFloat
var onCompletion: (() -> Void)?
init(offsetX: CGFloat, onCompletion: (() -> Void)? = nil) {
self.initialOffsetX = offsetX
self.offsetX = offsetX
self.onCompletion = onCompletion
}
var animatableData: CGFloat {
get { offsetX }
set {
offsetX = newValue
checkIfFinished()
}
}
func checkIfFinished() -> () {
if let onCompletion = onCompletion, offsetX == initialOffsetX {
DispatchQueue.main.async {
onCompletion()
}
}
}
func body(content: Content) -> some View {
content.offset(x: offsetX)
}
}
struct OffsetXEffectModifier_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Text("Hello")
.modifier(
OffsetXEffectModifier(offsetX: 10, onCompletion: {
print("Completed")
})
)
}
.frame(width: 100, height: 100, alignment: .bottomLeading)
.previewLayout(.sizeThatFits)
}
}
You can try VDAnimation library
Animate(animationStore) {
self.someState =~ newState
}
.duration(0.1)
.curve(.linear)
.start {
...
}

Is there any way to make a paged ScrollView in SwiftUI?

I've been looking through the docs with each beta but haven't seen a way to make a traditional paged ScrollView. I'm not familiar with AppKit so I am wondering if this doesn't exist in SwiftUI because it's primarily a UIKit construct. Anyway, does anyone have an example of this, or can anyone tell me it's definitely impossible so I can stop looking and roll my own?
You can now use a TabView and set the .tabViewStyle to PageTabViewStyle()
TabView {
View1()
View2()
View3()
}
.tabViewStyle(PageTabViewStyle())
As of Beta 3 there is no native SwiftUI API for paging. I've filed feedback and recommend you do the same. They changed the ScrollView API from Beta 2 to Beta 3 and I wouldn't be surprised to see a further update.
It is possible to wrap a UIScrollView in order to provide this functionality now. Unfortunately, you must wrap the UIScrollView in a UIViewController, which is further wrapped in UIViewControllerRepresentable in order to support SwiftUI content.
Gist here
class UIScrollViewViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let v = UIScrollView()
v.isPagingEnabled = true
return v
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.pinEdges(of: self.hostingController.view, to: self.scrollView)
self.hostingController.didMove(toParent: self)
}
func pinEdges(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
])
}
}
struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> UIScrollViewViewController {
let vc = UIScrollViewViewController()
vc.hostingController.rootView = AnyView(self.content())
return vc
}
func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
viewController.hostingController.rootView = AnyView(self.content())
}
}
And then to use it:
var body: some View {
GeometryReader { proxy in
UIScrollViewWrapper {
VStack {
ForEach(0..<1000) { _ in
Text("Hello world")
}
}
.frame(width: proxy.size.width) // This ensures the content uses the available width, otherwise it will be pinned to the left
}
}
}
Apple's official tutorial covers this as an example. I find it easy to follow and suitable for my case. I really recommend you check this out and try to understand how to interface with UIKit. Since SwiftUI is so young, not every feature in UIKit would be covered at this moment. Interfacing with UIKit should address most if not all needs.
https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
Not sure if this helps your question but for the time being while Apple is working on adding a Paging View in SwiftUI I've written a utility library that gives you a SwiftUI feel while using a UIPageViewController under the hood tucked away.
You can use it like this:
Pages {
Text("Page 1")
Text("Page 2")
Text("Page 3")
Text("Page 4")
}
Or if you have a list of models in your application you can use it like this:
struct Car {
var model: String
}
let cars = [Car(model: "Ford"), Car(model: "Ferrari")]
ModelPages(cars) { index, car in
Text("The \(index) car is a \(car.model)")
.padding(50)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(10)
}
You can simply track state using .onAppear() to load your next page.
struct YourListView : View {
#ObservedObject var viewModel = YourViewModel()
let numPerPage = 50
var body: some View {
NavigationView {
List(viewModel.items) { item in
NavigationLink(destination: DetailView(item: item)) {
ItemRow(item: item)
.onAppear {
if self.shouldLoadNextPage(currentItem: item) {
self.viewModel.fetchItems(limitPerPage: self.numPerPage)
}
}
}
}
.navigationBarTitle(Text("Items"))
.onAppear {
guard self.viewModel.items.isEmpty else { return }
self.viewModel.fetchItems(limitPerPage: self.numPerPage)
}
}
}
private func shouldLoadNextPage(currentItem item: Item) -> Bool {
let currentIndex = self.viewModel.items.firstIndex(where: { $0.id == item.id } )
let lastIndex = self.viewModel.items.count - 1
let offset = 5 //Load next page when 5 from bottom, adjust to meet needs
return currentIndex == lastIndex - offset
}
}
class YourViewModel: ObservableObject {
#Published private(set) items = [Item]()
// add whatever tracking you need for your paged API like next/previous and count
private(set) var fetching = false
private(set) var next: String?
private(set) var count = 0
func fetchItems(limitPerPage: Int = 30, completion: (([Item]?) -> Void)? = nil) {
// Do your stuff here based on the API rules for paging like determining the URL etc...
if items.count == 0 || items.count < count {
let urlString = next ?? "https://somePagedAPI?limit=/(limitPerPage)"
fetchNextItems(url: urlString, completion: completion)
} else {
completion?(pokemon)
}
}
private func fetchNextItems(url: String, completion: (([Item]?) -> Void)?) {
guard !fetching else { return }
fetching = true
Networking.fetchItems(url: url) { [weak self] (result) in
DispatchQueue.main.async { [weak self] in
self?.fetching = false
switch result {
case .success(let response):
if let count = response.count {
self?.count = count
}
if let newItems = response.results {
self?.items += newItems
}
self?.next = response.next
case .failure(let error):
// Error state tracking not implemented but would go here...
os_log("Error fetching data: %#", error.localizedDescription)
}
}
}
}
}
Modify to fit whatever API you are calling and handle errors based on your app architecture.
Checkout SwiftUIPager. It's a pager built on top of SwiftUI native components:
If you would like to exploit the new PageTabViewStyle of TabView, but you need a vertical paged scroll view, you can make use of effect modifiers like .rotationEffect().
Using this method I wrote a library called VerticalTabView 🔝 that turns a TabView vertical just by changing your existing TabView to VTabView.
You can use such custom modifier:
struct ScrollViewPagingModifier: ViewModifier {
func body(content: Content) -> some View {
content
.onAppear {
UIScrollView.appearance().isPagingEnabled = true
}
.onDisappear {
UIScrollView.appearance().isPagingEnabled = false
}
}
}
extension ScrollView {
func isPagingEnabled() -> some View {
modifier(ScrollViewPagingModifier())
}
}
To simplify Lorenzos answer, you can basically add UIScrollView.appearance().isPagingEnabled = true to your scrollview as below:
VStack{
ScrollView(showsIndicators: false){
VStack(spacing: 0){ // to remove spacing between rows
ForEach(1..<10){ i in
ZStack{
Text(String(i))
Circle()
} .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
}
}.onAppear {
UIScrollView.appearance().isPagingEnabled = true
}
.onDisappear {
UIScrollView.appearance().isPagingEnabled = false
}
}

Custom input accessory view Notification

At end of Advanced Notification Session 708 WWDC 2016. They talked about taking text input & action at same time.
Start at 24 min mark of WWDC session 708.
Where you take comment on party invite & accept or decline invite at same time. I tried to do that but am very unsuccessful.
class NotificationViewController: UIViewController, UNNotificationContentExtension {
var textField = UITextField(frame: CGRect(x: 0, y: 0, width: 100, height: 10))
override func viewDidLoad() {
super.viewDidLoad()
// Do any required interface initialization here.
}
override var canBecomeFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
customView.backgroundColor = UIColor.red
textField.placeholder = "Type Comment here"
textField.textColor = UIColor.black
customView.addSubview(textField)
print(customView)
return customView
}
func didReceive(_ response: UNNotificationResponse, completionHandler completion: #escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "comment" {
becomeFirstResponder()
textField.becomeFirstResponder()
print("Its working")
if let textResponse = response as? UNTextInputNotificationResponse {
print(textResponse.userText)
completion(UNNotificationContentExtensionResponseOption.doNotDismiss)
}
completion(UNNotificationContentExtensionResponseOption.doNotDismiss)
}
}
}
Not sure exactly what your problem is, but I recently faced a similar task - and I've successfully implemented the solution from your referred WWDC talk.
My problem was that I didn't know how to dismiss the notification when using a custom inputAccessoryView. My solution was to save the completionHandler and then call it when a specific button in my custom inputAccessoryView is clicked.
First; create the function variable to store the completionHandler:
var savedCompletionBlock:(UNNotificationContentExtensionResponseOption) -> Void = { (UNNotificationContentExtensionResponseOption) in }
Then; in func didReceive(_ response: UNNotificationResponse, completionHandler completion: #escaping (UNNotificationContentExtensionResponseOption) -> Void) save the completion block:
savedCompletionBlock = completion
And finally; call it wherever you need to (e.g. on a button click):
func confirmPressed() {
savedCompletionBlock(.dismiss)
}
If this doesn't help you let me know :)

Resources