view.endEditing causing app to freeze on some textFields - swift

Problem solved. See end of post.
Sorry if this is a bit long but I'm hoping I've included as much info to get this solved.
Brief overview of problem: Enter value in a textField using my custom keypad. Tap done button(should trigger view.endEditing) and some textFields will cause the app to freeze, most the time Xcode won't even throw an error but instead just restart the app, but i did catch one once(pic below). It works as expected on some textFields.
So I have a view controller with a bunch of textFields for the user to fill out which then performs calculations.
I have made a custom Keypad which essentially is the decimal pad with a "Done" button. I did this by making an keyboard.xib file and a keyboard.swift file.
Heres a snapshot of the error, I've included a whole bunch of my code below incase I'm using a method that isn't the best.
This is how the keyboard.swift file looks:
import UIKit
// The view controller will adopt this protocol (delegate)
// and thus must contain the keyWasTapped method
protocol KeyboardDelegate: class {
func keyWasTapped(character: String)
func keyDone()
func backspace()
}
class keyboard: UIView {
// This variable will be set as the view controller so that
// the keyboard can send messages to the view controller.
weak var delegate: KeyboardDelegate?
// MARK:- keyboard initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
let xibFileName = "Keyboard" // xib extention not included
let view = NSBundle.mainBundle().loadNibNamed(xibFileName, owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
// MARK:- Button actions from .xib file
#IBAction func keyTapped(sender: UIButton) {
// When a button is tapped, send that information to the
// delegate (ie, the view controller)
self.delegate?.keyWasTapped(sender.titleLabel!.text!) // could alternatively send a tag value
}
#IBAction func backspace(sender: UIButton) {
self.delegate?.backspace()
}
#IBAction func Done(sender: UIButton) {
self.delegate?.keyDone()
}
}
In the viewController I'm pretty sure I've included all the necessary things to access the keyboard seeing as it works for some textFields. Such as:
class myViewController: UITableViewController,UITextFieldDelegate, KeyboardDelegate
Then in viewDidLoad set each textField delegate:
self.textField1.delegate = self
self.textField2.delegate = self
self.textField3.delegate = self
// initialize custom keyboard
let keyboardView = keyboard(frame: CGRect(x: 0, y: 0, width: 0, height: numpad.height))
keyboardView.delegate = self // the view controller will be notified by the keyboard whenever a key is tapped
// replace system keyboard with custom keyboard
textField1.inputView = keyboardView
textField2.inputView = keyboardView
textField3.inputView = keyboardView
Then this function (which seems to me to be the problem):
func keyDone() {
view.endEditing(true)
//activeTextField.resignFirstResponder()
print("please dont freeze")
}
I have checked all the connections, they seem to be fine.
Let me know if I can add any more info to help work it out.
Many Thanks.
Solved!!!
I suppose ill just put it down to beating my head over it rather than taking a break from the screen! Still I'm confused why it wasn't given a more specific error.
The problem was that in some cases one of the functions was dividing by zero (this is undefined... not possible) but a good thing to take from this(thank you Olivier) is the Instruments Tools to help find where abouts the code was losing its mind. This tutorial helped me understand how to use instruments! So once I could see where it was going crazy I set up a bunch of print statements to watch the values as they went into the 'problem' calculation, where I found the denominator to be zero. Bit of rearranging the code around to avoid this and problem solved!

This error message is basically saying that there is a memory issue, try running the code with instruments (Allocations in particular) this might reveal is there is something amiss with your keyboard
Edit 2: for anyone finding this error message in future (actual solution in this case)
Double check any code code running after keyDone() to see if there are any infinite loops or situations that would cause the compiler to assume an infinite amount of memory is required. In this case a line of code was dividing by zero, causing a fatal memory error (unable to allocate the N/A value it generated)

Related

NSComboBox disappears immediately after click

I'm working on an application which behaves similarly to Spotlight. However, currently I'm having a trouble with NSComboBox.
To show the application after hitting a hotkey I use activate(ignoringOtherApps: true) and NSWindowController(window: window).showWindow(self). Then when user hits Escape I do window.close() and hide(self).
Everything works great, previous application gets focus. However, when I open again the window, first click on NSComboBox causes a very strange behavior (like on the movie below). First time it instantly disappears.
I found out that it happens because of NSApp.hide. When I don't call it, everything works well. However, I need to call it, because I want the previous app to get the focus.
To workaround this issue I can replace NSWindow with nonactivating NSPanel. It resolves the problem. However, it's not possible in my case because I need to use it also with presentAsModalWindow and presentAsSheet where I can't control whether it's a window or panel.
I also discovered that a single click on window's background before clicking on ComboBox helps. So it seems like this window doesn't have focus, but looks like focused. I also tried all methods like makeKeyAndOrderFront, becomeFirstResponder, NSApp.unhide etc. etc. Nothing helps.
Under the hood NSComboBox has its own window NSComboBoxWindow, so my guess is that when I click it opens its window and then it receives information that the parent window took focus and dismisses itself for some reason.
I'm not sure if this is Cocoa bug or what. Is there any way to fix it?
Minimum Reproducible Example
Create new macOS project with NSComboBox and NSButton. Connect button to IBAction.
import Cocoa
class ViewController: NSViewController {
#IBAction func close(_ sender: Any) {
view.window?.close()
NSApp.hide(self)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
self.view.window?.makeKeyAndOrderFront(self)
NSApp.activate(ignoringOtherApps: true)
}
}
}
Workaround
Finally, I managed to create a workaround. It's ugly but it works. I cover arrow part with a transparent view, intercept click and invoke two times expand via accessibility...
import Cocoa
final class ClickView: NSView {
var onMouseDown: () -> (Bool) = { return false }
override func mouseDown(with event: NSEvent) {
if !onMouseDown() {
super.mouseDown(with: event)
}
}
}
final class FixedComboBox: NSComboBox {
private let clickView = ClickView()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
fix()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
fix()
}
private func fix() {
clickView.onMouseDown = { [weak self] in
guard let cell = self?.cell else { return false }
// first expand will be immediately closed because of Cocoa bug
cell.setAccessibilityExpanded(true)
// we need to schedule another one with a small delay to let it close the first call
// this one almost immediately to avoid blinking
DispatchQueue.main.async { cell.setAccessibilityExpanded(true) }
// in case the first one didn't "catch" the right moment (sometimes it happens)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { cell.setAccessibilityExpanded(true) }
return true
}
addSubview(clickView)
clickView.snp.makeConstraints { make in
make.width.equalTo(20)
make.trailing.top.bottom.equalToSuperview()
}
}
}

Swift: Need guidance if this is a good approach on making nested UIView in VC

Hi so right now I'm making some sort of camera app with some basic button like turn on flash, enable grid, etc.
The cameraViewController manages the view. And it has the UIView for previewing what to capture, and at the bottom, a UIView for the buttons, and shutter button.
Right now, this is how I approach it.
ShutterButtonUIView inside CameraEditorUIView, and CameraEditorUIView inside CameraViewController.
And my editor buttons (flash, grid, filters, etc) is of their own class. And I insert it into my view like so. That way, I promote separation of concern and my class won't be cluttered by all the many buttons.
let editorButtons = ActionButtonFactory.shared.editorButtons
for button in editorButtons {
// Insert to CameraEditorUIView
}
And example of one of my editor button class
class FilterAction: UIButton {
private let filter = ["paintbrush.fill"]
override init(frame: CGRect) {
super.init(frame: frame)
self.setImage(getIcon(), for: .normal)
self.addTarget(self, action: #selector(filterTapped), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc private func filterTapped() {
TapticHelper.shared.lightTaptic()
NotificationCenter.default.post(name: .presentFilterListVC, object: nil) //Not sure if this is a good approach
}
private func getIcon() -> UIImage {
return IconHelper.shared.getIconImage(iconName: filter[0])
}
}
This makes my line of code very neat and small, good. But is it a good approach? I can't do any view controller functions such as presenting any alert, instantiate another VC, and such from the UIView.
So let's say After pressing the shutter button I want to go to imageDetailVC, My approach is to use NotificationCenter observer pattern.
I'm not sure if this is a good approach or not. Or should I put everything under CameraViewController?
Also, another reason I have doubt on this approach is when I need to disable the rule of third grid for example, I need to make it a static so that my other UIView can access it.
ie: A button from another class
I'm not sure if this is the best approach, But I've figured that instead of using UIView, I used UIViewController. And add them as a child VC. That way, I can still access the parent without coupling with notifications and make it harder.
So if I was to instantiate a new VC, I just used self.parent.

Swift macOS SegmentedControl Action not getting called

Description
I am trying to use NSSegmentedControls to transition between Child ViewControllers. The ParentViewController is located in Main.storyboard and the ChildViewControllers are located in Assistant.storyboard. Each ChildViewController has a SegmentedControl divided into 2 Segments and their primary use is to navigate between the ChildViewControllers. So they are set up as momentaryPushIn rather than selectOne. Each ChildViewController uses a Delegate to communicate with the ParentViewController.
So in the ParentViewController I added the ChildViewControllers as following:
/// The View of the ParentViewController configured as NSVisualEffectView
#IBOutlet var visualEffectView: NSVisualEffectView!
var assistantChilds: [NSViewController] {
get { return [NSViewController]() }
set(newValue) {
for child in newValue { self.addChild(child) }
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
addAssistantViewControllersToChildrenArray()
}
override func viewWillAppear() {
visualEffectView.addSubview(self.children[0].view)
self.children[0].view.frame = self.view.bounds
}
private func addAssistantViewControllersToChildrenArray() -> Void {
let storyboard = NSStoryboard.init(name: "Assistant", bundle: nil)
let exampleChild = storyboard.instantiateController(withIdentifier: "ExampleChild") as! ExampleChildViewController
let exampleSibling = storyboard.instantiateController(withIdentifier: "ExampleSibling") as! ExampleSiblingViewController
exampleChild.navigationDelegate = self
exampleSibling.navigationDelegate = self
assistantChilds = [exampleChild, exampleSibling]
}
So far so good. The ExampleChildViewController has an NSTextField instance. While I am in the scope of the TextField, I can trigger the action of the SegmentedControls. Its navigating forward and backward as it should. But once I leave the scope of the TextField I can still click the Segments, but they are not triggering any action. They should be able to navigate forward and backward even if the TextField is not the current "First Responder" of the application. I think I am missing something out here, I hope anyone can help me with this. I know the problem is not the NSSegmentedControl because I am seeing the same behavior with an NSButton, which is configured as Switch/Checkbox, in the SiblingViewController. I just don't have any idea anymore what I am doing wrong.
It`s my first time asking a question myself here, so I hope the way I am doing is fine for making progress with the solution. Let me know if I can do something better/different or if I need to provide more information about something.
Thanks in advance!
Additional Information
For the sake of completeness:
The ParentViewController itself is embedded in a ContainerView,
which is owned by the RootViewController. I can't imagine this does
matter in any way, but this way we are not missing something out.
I am actually not showing the navigation action, because I want to
keep it as simple as possible. Furthermore the action is not problem,
it does what I want it to do. Correct me if I am wrong with this.
Possible solutions I found while researching, which did not work for me:
Setting window.delegate of the ChildViewControllers to NSApp.windows.first?.delegate
Setting the ChildViewController to becomeFirstResponder in its func viewWillAppear()
visualEffectView.addSubview(self.children[0].view, positioned: NSWindow.OrderingMode.above, relativeTo: nil)
Related problems/topics I found while researching:
Basic segmented control not working
Adding and Removing Child View Controllers
NSSegmentedControl - Odd appearance when placed in blur view
How to set first responder to NSTextView in Swift?
How to use #selector in Swift 2.2 for the first responder
Accessing methods, actions and/or outlets from other controllers with swift
How to use Child View Controllers in Swift 4.0 programmatically
Container View Controllers
issues with container view
Control a NSTabViewController from parent View
How to detect when NSTextField has the focus or is it`s content selected cocoa
SOLUTION
let parentViewControllerInstance = self.parent as! ParentViewController
segmentedControl.target = parentViewControllerInstance
In my case I just had to set the delegate as the target of the sendAction method.
Background
Ok, after hours of reading the AppKit Documentation I am now able to answer my own question.
First, debugging the UI showed that the problem was definitely not in the ViewHierarchy.
So I tried to think about the nature of NSButton and NSSegmentedControl. At some point I noticed that both are subclasses of NSControl.
class NSSegmentedControl : NSControl
class NSButton : NSControl
The AppKit Documentation says:
Discussion
Buttons are a standard control used to initiate actions within your app. You can configure buttons with many different visual styles, but the behavior is the same. When clicked, a button calls the action method of its associated target object. (...) You use the action method to perform your app-specific tasks.
The bold text points to the key of the solution – of its associated target object. Typically I define the action of an control item like this:
button.action = #selector(someFunc(_:))
This causes the NSControl instance to call this:
func sendAction(_ action: Selector?, to target: Any?) -> Bool
Parameter Description from the documentation:
Parameters
theAction
The selector to invoke on the target. If the selector is NULL, no message is sent.
theTarget
The target object to receive the message. If the object is nil, the application searches the responder chain for an object capable of handling the message. For more information on dispatching actions, see the class description for NSActionCell.
In conclusion the NSControl instance, which was firing the action method (in my case the NSSegmentedControl), had no target to send its action to. So it was only able to send its action method across the responder chain - which obviously has been nil while the first responder was located in another view.

Swift fatal error: unexpectedly found nil while unwrapping an Optional value on segue

I know this is a crazy asked question, but I still can't figure it out. I am using a library for creating a circular progress bar (KDCircularProgress). Everything works fine, but the moment I try to call and perform a segue (for changing the ViewController), the app crashes with the "optional value" error.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
// Gauge pressure
var frontGaugeProgressLeft: KDCircularProgress! //define the variable here
...
override func viewDidLoad() {
super.viewDidLoad()
...
frontGaugeProgressLeft = KDCircularProgress(frame: CGRect(x: 0, y: 0, width: 125, height: 125))
frontGaugeProgressLeft. ... //do some settings here
// And display the progress bar to the view
view.addSubview(frontGaugeProgressLeft) //if this is commented, app works
...
}
...
// Show settings/preferences menu
func panAction(sender: UIScreenEdgePanGestureRecognizer) {
if sender.edges == UIRectEdge.Right {
performSegueWithIdentifier("showMenuSegue", sender: nil)
}
}
...
}
When I swipe left and trigger the segue, for changing the ViewController, the app crashes. I narrowed it down to that view.addSubview line. If I comment the line, app works. Tried some things, but still no clue how to fix this. Appreciate the support!
Fixed. There was some method in the library. Commented that since it was not used, everything alright.
// public override func didMoveToWindow() {
// progressLayer.contentsScale = window!.screen.scale
// }

Simple NSPageController example throws an unknown subview warning and stops working

I'm trying to get a very basic NSPageController to work (in book mode, not history mode). It will successfully transition once, and then stop working.
I suspect I'm creating the NSImageViews I'm loading into it wrong, but I can't figure out how.
The storyboard has a the SamplePageController which holds in initial hard-coded NSImageView.
I suspect I'm missing something really obvious here, since all of the tutorial's I've found for NSPageController are in Objective C not swift, and tend to focus on the history view mode.
The code is:
import Cocoa
class SamplePageController: NSPageController, NSPageControllerDelegate {
private var images = [NSImage]()
#IBOutlet weak var Image: NSImageView!
//Gets an object from arranged objects
func pageController(pageController: NSPageController, identifierForObject object: AnyObject) -> String {
let image = object as! NSImage
let image_name = image.name()!
let temp = arrangedObjects.indexOf({$0.name == image_name})
return "\(temp!)"
}
func pageController(pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
let controller = NSViewController()
let imageView = NSImageView(frame: Image.frame)
let intid = Int(identifier)
let intid_u = intid!
imageView.image = images[intid_u]
imageView.sizeToFit()
controller.view = imageView
return controller
// Does this eventually lose the frame since we're returning the new view and then not storing it and the original ImageView is long gone by then?
// Alternatively, are we not sizing the imageView appropriately?
}
override func viewDidLoad() {
super.viewDidLoad()
images.append(NSImage(named:"text")!)
images.append(NSImage(named:"text-2")!)
arrangedObjects = images
delegate = self
}
}
In this case your pageController.view is set to your window.contentView and that triggers the warning. What you need to do is add a subview in the window.contentView and have your pageController.view point to that instead.
The reason for the warning is that since NSPageController creates snapshots (views) of your content history, it will add them at the same level as your pageController.view to transition between them: that means it will try to add them to pageController.view.superview.
And if your pageController.view is set to window.contentView, you are adding subviews to the window.contentView.superview, which is not supported:
New since WWDC seed: NSWindow has never supported clients adding subviews to anything other than the contentView.
Some applications would add subviews to the contentView.superview (also known as the border view of the window). NSWindow will now log when it detects this scenario: "NSWindow warning: adding an unknown subview:".
Applications doing this will need to fix this problem, as it prevents new features on 10.10 from working properly. See titlebarAccessoryViewControllers for official API.