Problems with NSOpenPanel and massive memory leak - swift

I am developing a GUI for rsync (RsyncOSX) and in next version the plan is add a GUI for choosing catalogs. I have some time ago experienced some stability issues utilizing NSOpenPanel but now I wanted to try again. But still there seems to issues utilizing NSOpenPanel, the console is producing the following error:
Class FIFinderSyncExtensionHost is implemented in both /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit (0x7fff8c017210) and /System/Library/PrivateFrameworks/FileProvider.framework/OverrideBundles/FinderSyncCollaborationFileProviderOverride.bundle/Contents/MacOS/FinderSyncCollaborationFileProviderOverride (0x10f1d5dc8). One of the two will be used. Which one is undefined.
I have also run the app through Xcode instruments and checked for memory leaks and every time opening the NSOpenPanel there is a huge memory leak..
Has anyone found a workaround? The code for opening the GUI is very simple :
private func openfiledlg (title: String, message: String) {
let openPanel = NSOpenPanel()
openPanel.prompt = "Select"
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = true
openPanel.canChooseFiles = false
openPanel.resolvesAliases = true
openPanel.title = title
openPanel.message = message
if self.modal {
let OK = openPanel.runModal()
if OK.rawValue == NSApplication.ModalResponse.OK.rawValue {
self.urlpath = openPanel.url
}
} else {
openPanel.begin(completionHandler: { response in
if response.rawValue == NSFileHandlingPanelOKButton {
self.urlpath = openPanel.url
}
openPanel.close()
})
}
}
Setting weak var openPanel = NSOpenPanel() only reduces the memory leak..

If the window is set to be released when closed, a release message is sent to the object after the current event is completed. For an NSWindow object, the default is to be released on closing, while for an NSPanel object, the default is not to be released. You can use the isReleasedWhenClosed property to change the default behavior.

Related

macOS: AVAssetWriter crash upon markAsFinished / finishWriting

Using AVAssetWriter to save captured audio and video to a file. It works correctly on my own machines all the time, but Apple's App Review team reports a crash when finishing recording.
In the report the thread crashes upon finishWriting(completionHandler:).
My code to stop writing:
var videoWriterInput: AVAssetWriterInput!
var audioWriterInput: AVAssetWriterInput!
var videoWriter: AVAssetWriter!
var isRecording = false
func stopVideoWriter() async {
guard isRecording else { return }
isRecording = false
videoWriterInput.markAsFinished()
audioWriterInput.markAsFinished()
videoWriter.finishWriting { [self] in
videoWriter = nil
videoWriterInput = nil
audioWriterInput = nil
}
}
Some SO posts mention to check videoWriter.status before stopping, but this does not seem to help at all. Also there is no documentation that shows it is necessary to check the status before finishing.
Apple's documentation for finishWriting(completionHandler:) mentions the following:
To ensure the asset writer finishes writing all samples, call this
method only after all calls to append(:) or
append(:withPresentationTime:) return.
The app works fine on my own testing machines, so I cannot confirm that this is the actual part where the crash happens.
Any ideas?

SwiftUI - Stripe STPPaymentContext view not updating with STPPaymentConfiguration

So I have implemented Stripe into my project. Everything works as intended. However, I do wish to use some of the configuration options available to use. For instance, setting the default payment country to "UK". However, this causes my app to crash as it finds a nil value. I am also trying to use some of the other settings like so:
self.config.requiredBillingAddressFields = .full
self.config.appleMerchantIdentifier = "dummy-merchant-id"
self.config.cardScanningEnabled = true
self.config.applePayEnabled = true
The only thing within the view that I can seem to change is the .requiredBillingAddressFields. Everything else does not seem to register. My code is as followed, I've ripped out what is not related to Stripe for clarity:
struct CheckOutView: View {
#StateObject var paymentContextDelegate: PaymentContextDelegate
let config = STPPaymentConfiguration()
#State private var paymentContext: STPPaymentContext!
var body: some View {
HStack {
BorderedButton(text: "Pay Now") {
self.paymentContext.requestPayment()
}
Spacer()
}
HStack {
BorderedButton(text: paymentContextDelegate.paymentMethodButtonTitle) {
self.paymentContext.presentPaymentOptionsViewController()
}
Spacer()
}
.onAppear() {
DispatchQueue.main.async {
self.paymentContextConfiguration()
}
}
}
//MARK: - Configuration
func paymentContextConfiguration() {
self.config.requiredBillingAddressFields = .full
self.config.appleMerchantIdentifier = "dummy-merchant-id"
self.config.cardScanningEnabled = true
self.config.applePayEnabled = true
self.config.verifyPrefilledShippingAddress = true
self.config.canDeletePaymentOptions = true
self.paymentContext = STPPaymentContext(customerContext: customerContext, configuration: self.config, theme: .defaultTheme)
self.paymentContext.delegate = self.paymentContextDelegate
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).last
self.paymentContext.hostViewController = keyWindow?.rootViewController
}
}
Any help would be appreciated! Thank you.
This is more of a guess, but perhaps the config options are applying, but it's just that the other requirements for the features they enable are not met? How exactly do you determine that everything else except billing address does not register?
For example applePayEnabled will only result in the Apple Pay option appearing if the device itself supports Apple Pay(on a physical device with a real card in your wallet it would work(but you must have a real Apple Merchant ID), on simulators it can be patchy).
For cardScanningEnabled maybe you don't have the entitlement enabled in your app? https://github.com/stripe/stripe-ios#card-scanning-beta
A simple way to check if the config applies is maybe to set requiredShippingAddressFields since it's self-contained and very visual.

Accessing NSWindow-like properties in Catalyst macOS app

I am thinking about porting my macOS app to Catalyst.
My app shows a transparent window (no title bar, clear background) on top of all other apps windows (dock included).
To do that, in the non-catalyst code I use:
window.isOpaque = false
window.hasShadow = false
window.backgroundColor = .clear
window.styleMask = .borderless
window.isMovableByWindowBackground = true
window.level = .statusBar
Using UIKit, I was only able to remove the toolbar so far:
window.titleBar.titleVisibility
...But no clue about the other settings.
I plan to make the app available on the App Store in the future, but if the only way to do so is and hack with a private API, that's fine.
Any ideas?
Thanks in advance
There is no official API for doing that, but you can easily access the NSWindow instance and modify it directly. You can do that manually or using some library like Dynamic (Full disclosure: I'm the author):
let window = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(uiWindow)
window.isOpaque = false
window.hasShadow = false
window.backgroundColor = Dynamic.NSColor.clearColor
window.styleMask = 0 /*borderless*/
window.isMovableByWindowBackground = true
window.level = 25 /*statusBar*/
I have some success removing the close button on Catalyst by calling a function from the viewDidAppear(). I called it AppDelegate().disableTitleBarButtons(). Has to be from view did appear.
AppDelegate().disableTitleBarButtons() is as follows
func disableTitleBarButtons() {
func bitSet(_ bits: [Int]) -> UInt {
return bits.reduce(0) { $0 | (1 << $1) }
}
func property(_ property: String, object: NSObject, set: [Int], clear: [Int]) {
if let value = object.value(forKey: property) as? UInt {
object.setValue((value & ~bitSet(clear)) | bitSet(set), forKey: property)
}
}
// disable full-screen button
if let NSApplication = NSClassFromString("NSApplication") as? NSObject.Type,
let sharedApplication = NSApplication.value(forKeyPath: "sharedApplication") as? NSObject,
let windows = sharedApplication.value(forKeyPath: "windows") as? [NSObject]
{
for window in windows {
let resizable = 4
property("styleMask", object: window, set: [], clear: [resizable])
let fullScreenPrimary = 7
let fullScreenAuxiliary = 8
let fullScreenNone = 9
property("collectionBehavior", object: window, set: [fullScreenNone], clear: [fullScreenPrimary, fullScreenAuxiliary])
}
}
}
Where is says let resizable = 4,
Change to 3 for no Maximise,
Change to 2 for No minimise,
Change to 1 of No Close button.
Play with the other numbers or stylemask settings also. Good luck

How to fix "EXC_BAD_ACCESS" when creating WKPreferences variable in Swift 4

In my code, I have a part that creates a new WKWebView with a specific WKWebViewConfiguration, which in turn has a WKPreferences reference. All of this then gets added to the view of the application.
The problem is that up until this point, my code has been running perfectly, with no problems.
Now, for some bizarre reason, when I launch the application,
I get
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeec686fc0)
on the line when I create a variable for the WKPreferences.
I am working with Xcode 10.1, Swift 4, and I have Alamofire and NetworkReachability pods installed. I have tried just creating the WKWebView without the WKPreferences, but the error just moves on to the WKWebViewConfiguration instead.
func createWebView() {
let preferences = WKPreferences() //<-- EXC_BAD_ACCESS
preferences.javaScriptEnabled = true
let webConfiguration = WKWebViewConfiguration()
webConfiguration.preferences = preferences
webConfiguration.allowsInlineMediaPlayback = true
webViewVar = WKWebView(frame: self.view.bounds, configuration: webConfiguration)
webViewVar.uiDelegate = self
self.view = webViewVar
}
override func loadView() {
createWebView()
}
The expected behavior is that the app would launch and show a web page, that doesn't change, specified elsewhere in the code. The actual result is that the app crashes with the EXC_BAD_ACCESS error upon startup.
I'd have to say you've found a bug. For some reason, the runtime isn't letting you create a WKPreferences object this early in the life of the app.
The workaround is that you'll have to postpone creation of the web view until the app is up and running. To do so, delete loadView and implement viewDidLoad instead, and do all the work there, making the web view a subview of the main view rather than trying to make it be the main view.
var webViewVar : WKWebView!
func createWebView() {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let webConfiguration = WKWebViewConfiguration()
webConfiguration.preferences = preferences
webConfiguration.allowsInlineMediaPlayback = true
webViewVar = WKWebView(frame: self.view.bounds, configuration: webConfiguration)
webViewVar.uiDelegate = self
self.view.addSubview(webViewVar)
webViewVar.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
override func viewDidLoad() {
super.viewDidLoad()
createWebView()
}
It's annoying, and you should file a bug report with Apple, but at least this will keep you going for now.

Programmatically disabling screenshot in App

I want to prevent taking screenshot of a page in app.
how to do it programmatically so that screenshots cannot be taken.
Found code to detect screenshot. Can it be deleted as soon as a screenshot is taken?
let mainQueue = NSOperationQueue.mainQueue()
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationUserDidTakeScreenshotNotification,
object: nil,
queue: mainQueue) { notification in
// executes after screenshot
}
There is no way to prevent ScreenShots but you can prevent Screen Recording
through this code.
func detectScreenRecording(action: #escaping () -> ()) {
let mainQueue = OperationQueue.main
NotificationCenter.default.addObserver(forName: UIScreen.capturedDidChangeNotification, object: nil, queue: mainQueue) { notification in
// executes after screenshot
action()
}
}
//Call in vewWillApper
detectScreenRecording {
print(UIScreen.main.isCaptured)
if UIScreen.main.isCaptured {
//your vier hide code
print("self.toHide()")
} else {
// self.sceneDeleg(ate?.window?.isHidden = false
//your view show code
print("self.toShow()")
}
}
There is absolutely no way to completely prevent user from taking screenshot during the app process, and that's because you do not have access to delete photos in the photo gallery of the user. It would totally be a security issue if you could access your user's photos.
However, there are ways to partially prevent screenshots, as described here: Prevent screen capture in an iOS app
Technically that is possible, via the Photos framework, the docs for which can be found here.
Example code can be found here.
However, this will ask the user's permission first, and then again to confirm deletion; so possibly not the ideal solution. Unfortunately this is as good as it gets as Apple has the Camera Roll fairly locked down.
You cannot prevent user from taking screenshot, however, you can hide the content while a screenshot is taken, Use this code to do so..
extension UIView {
func hideContentOnScreenCapture() {
DispatchQueue.main.async {
let field = UITextField()
field.isSecureTextEntry = true
self.addSubview(field)
field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(self.layer)
}
}
}
Usage:
yourView.hideContentOnScreenCapture()