Swift: Set properties to an Extension of IBOutletCollection containing UIButtons - uibutton

How can I set properties to an "extension" of an "IBOutletCollection" containing "UIButtons"? I am trying to do this in a similar way that one can do with an extension UIButton, that does not seem to extend to a collection of UIButtons. I hope to be as thorough and clear as possible.
Although there are similar questions:
1.Change the color of UIButton in IBOutletCollection
2.Set Individual Title of UIButtons in IBOutletCollection
...and numourous others. I still am not grasping how to do this, and can really use help that any of you can provide.
Here is what I have gathered so far:
An IBOutletCollection is always an NSArray.
Use a for-in-loop to iterate through the array’s content.
Here is my code example: ( I have tried replacing where “Array” is with: “NSArray” & “Collection” with “Collection” working similar to “Array”)
//Inside the main viewController:
#IBOutlet var ButtonCollection: [UIButton]!
//Inside the extention file:
extension Array where Element == UIButton
{
func sharedButtonProperties()
{
for button in ButtonCollection
{
self.setTitleColor(.red, for: .normal)
//MARK: More properties will go here, once I get this to work.
}
}
}
//calling the code inside main viewController, within "viewDidAppear"
ButtonCollection.sharedButtonProperties()
Now here is an example of an extension of a single IBOutlet of a UIButton that is working just fine, but not with a Collection:
//Inside the main viewController:
#IBOutlet weak var ButtonSingle: UIButton?
//Inside the extention file:
extension UIButton
{
func buttonProperties()
{
self.setTitleColor(.red, for: .normal)
}
}
//calling the code inside main viewController, within "viewDidAppear"
ButtonSingle?.buttonProperties()
I am not an expert coder for sure, any help provided is greatly appreciated.
Thank you.

I have finally found the solution after so much trial and error. I want to share the answer with anybody that needs to set properties to UIButtons within an IBOutletCollection. This is a very efficient way to cut down time when you want to set/define properties to a group of buttons, or to all buttons project-wide.
I really hope someone finds this useful!
Code (Tested & Works): Note that what is in bold is what changed from the code I had in question.
//Inside the main viewController
#IBOutlet var ButtonCollection: [UIButton]!
//Inside the extention file:
extension Array where Element == UIButton
{
func sharedButtonProperties()
{
for **buttonGrp** in **self**
{
**buttonGrp**.setTitleColor(.red, for: .normal)
//MARK: More properties will go here now!
}
}
}
//calling the code inside main viewController, within "viewDidAppear"
ButtonCollection.sharedButtonProperties()
As you can see: I almost had it figured out, but all that was needed was to call the “buttonGrp” for the “for-in-loop", and to set this in “self”. I hope that makes sence!
Bye bye!

Related

menuWillOpen: and menuDidClose: not invoked for NSMenuDelegate

[Edit] as Willeke helpfully points out it's menuDidClose: NOT menuWillClose:. My code actually had that part right. Correcting the post in case someone else finds this researching a similar problem.
I'm sure this is just a Cocoa newbie problem but I've wracked my brain on it for hours. I've read the NSMenu and NSMenuDelegate docs a few times trying to figure out what I'm missing but it looks straight forward.
I have a window controller for a preferences window with a toolbar and three views. The window controller is declared as NSMenuDelegate.
#interface PrefsController : NSWindowController <NSMenuDelegate, NSWindowDelegate, NSOpenSavePanelDelegate>
This issue is a NSPopUpButton on the first view. The menu associated with popupbutton works fine. I can modify, etc. the menu via the associated IBOutlet variable. It's bound to Shared User Defaults Controller for selected value and that works fine.
But the menuWillOpen: and menuDidClose: methods are not invoked when the menu is accessed.
- (void)menuWillOpen:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(32, 32)];
}
}
- (void)menuDidClose:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(16, 16)];
}
}
My apologies for what is almost certainly a dumb mistake on my part, but I'm stumped.
Menu delegates are not used that often, so Apple hasn't made them too easy to set up in Interface Builder. Instead, do this in awakeFromNib:
myPopupButton.menu.delegate = self;

Autolayout is not working with NSSplitView and NSPageController with Storyboard. Is this Apple Bug?

I just created a empty project on github --> here <-- to demonstrate the problem (Done in objective-c)
The project is a simple storyboard project. An NSWindowController loads an NSPageController which loads a NSSplitView containing 3 panes. There is no code in the sample project, except the code to load the screens. When the project runs, it looks like this .
How do I get the constraints to make the splitView stretch all the way to the ends when the window is resized? The weird thing is, if you Switch the NSWindowController's contentController from the NSPageController to the NSSplitViewController, then every thing works as expected. Is this Apple Bug? I would appreciate any answer swift/objectivec please. I've tried but nothing works.
[EDIT] - Based on the answer below and further researching (contacted Apple), it appears that NSPageViewController does not use autolayout constraints but relies on autoresizing mask and frame setting on its children views.
So when the page controller creates its view controllers, we should set this:
-(NSViewController *)pageController:(NSPageController *)pageController viewControllerForIdentifier:(NSString *)identifier {
NSViewController *viewController = [self.storyboard instantiateControllerWithIdentifier:identifier];
[viewController.view setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)];
return viewController;
}
With this, the problem is fixed. I wish as an update in future, this control works with Autolayout constraints as it seems more natural.
I had many problems with NSPageController. I found that the solution is to not use auto layout with it.
Try to use the NSAutoresizingMaskOptions on your NSSplitView.
First, remove all constraints inside the NSPageController.
Then:
splitView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
splitView.frame = pageController.view.bounds;
or
splitView.autoresizingMask = [.ViewWidthSizable, .ViewHeightSizable]
splitView.frame = pageController.view.bounds
EDIT
Made a project based on yours here
I also got same issue in Pagecontroller:
And solved it by using the given code:
func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
switch identifier {
case "formController":
let controller = NSStoryboard(name: "Main", bundle:nil).instantiateController(withIdentifier: "formController") as! FormController
controller.view.autoresizingMask = [.height , .width]
return controller
default:
return self.storyboard?.instantiateController(withIdentifier: identifier) as! NSViewController
}
}
func pageController(_ pageController: NSPageController, identifierFor object: Any) -> String {
return String(describing: object)
}
func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
print(pageController.selectedIndex)
pageController.completeTransition()
pageController.selectedViewController!.view.autoresizingMask = [.height , .width]
}

NSBox is not showing methods to add content in it (Swift)

I would need to add some contents inside a NSBox, an Image and some text.
To do that I use the methods of NSBox class and call them on the object.
I created an IBOutlet:
#IBOutlet weak var theBox: NSBox!
and I also have a button, once I click it, the box will be filled with text (NSTextField):
#IBAction func filler(sender: AnyObject)
{
var label: NSTextField! = NSTextField() //Initialized a label to put it in the box
label.stringValue = "My text hey hey hey!"
theBox.setContentView(label) //I GET AN ERROR MESSAGE HERE
}
to add the content I used .setContentViewbut this method is not callable on the object theBoxand I don't know why I am wrong. Look at the documentation. The method exists and it is callable on the NSBox objects.
I'm new with swift and OSX development. Any Idea?
In Swift, you don't call the set methods for properties, you just assign to them directly:
theBox.contentView = label

NSStoryboardSegue sample code (Yosemite Storyboard)

OS X Yosemite introduced NSStoryboardSegue
“A storyboard segue specifies a transition or containment relationship between two scenes in a storyboard…”
Update:
• If I attempt to use a NSStoryboardSegue subclass in a Storyboard with Yosemite., it crashes with SIGABRT.
• If I ignore segues, and manually present a view controller using a specified, custom animator for presentation and dismissal,
func presentViewController(_ viewController: NSViewController,
animator animator: NSViewControllerPresentationAnimator)
it works as expected.
This post provides additional insight: Animate custom presentation of ViewController in OS X Yosemite
Using that as a reference, here's my attempt so far:
class FadeSegue: NSStoryboardSegue {
override func perform() {
super.perform()
sourceController.presentViewController(destinationController as NSViewController,
animator: FadeTransitionAnimator())
}
}
class FadeTransitionAnimator: NSObject, NSViewControllerPresentationAnimator {
func animatePresentationOfViewController(toViewController: NSViewController, fromViewController: NSViewController) {
toViewController.view.wantsLayer = true
toViewController.view.layerContentsRedrawPolicy = .OnSetNeedsDisplay
toViewController.view.alphaValue = 0
fromViewController.view.addSubview(toViewController.view)
toViewController.view.frame = fromViewController.view.frame
NSAnimationContext.runAnimationGroup({ context in
context.duration = 2
toViewController.view.animator().alphaValue = 1
}, completionHandler: nil)
}
func animateDismissalOfViewController(viewController: NSViewController, fromViewController: NSViewController) {
viewController.view.wantsLayer = true
viewController.view.layerContentsRedrawPolicy = .OnSetNeedsDisplay
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = 2
viewController.view.animator().alphaValue = 0
}, completionHandler: {
viewController.view.removeFromSuperview()
})
}
}
The problem appears to be with the Swift 'subclassing' of NSStoryboardSegue. If you implement the same functionality using Objective-C, everything works as expected. The problem is specifically with your FadeSeque class. The animator object works fine in either Objective-C or Swift.
So this:
class FadeSegue: NSStoryboardSegue {
override func perform() {
super.perform()
sourceController.presentViewController(destinationController as NSViewController,
animator: FadeTransitionAnimator())
}
}
Will work if provided as an Objective-C class:
#interface MyCustomSegue : NSStoryboardSegue
#end
#implementation FadeSegue
- (void)perform {
id animator = [[FadeTransitionAnimator alloc] init];
[self.sourceController presentViewController:self.destinationController
animator:animator];
}
#end
(I don't think you need to call super )
As this doesn't seem to be documented much anywhere, I have made a small project on github to demonstrate:
NSStoryboardSegue transitions from one NSViewController to another in the same Storyboard
NSViewController present: methods to achieve the same affect to a separate Xib-based NSViewController without using a Storyboard Segue
presentViewController:asPopoverRelativeToRect:ofView:preferredEdge:behavior:
presentViewControllerAsSheet:
presentViewControllerAsModalWindow:
presentViewController:animator:
animator and segue objects in Objective-C and Swift
edit
OK I've tracked down the EXC_BAD_ACCESS issue. Looking in the stack trace it seemed to have something to do with (Objective-C) NSString to (Swift) String conversion.
That made wonder about the identifier property of NSStoryboardSegue. This is used when setting up segues in the Storyboard, and is not so useful in Custom segues created in code. However, it turns out that if you set an identifier in the storyboard to any string value, even "", the crash disappears.
The identifier property is an NSString* in Objective-C
#property(readonly, copy) NSString *identifier
and an optional String in Swift:
var identifier: String? { get }
Note the read-only status. You can only set the identifier on initialising the object.
The designator initialiser for NSStoryboardSegue looks like this in Objective-C:
- (instancetype)initWithIdentifier:(NSString *)identifier
source:(id)sourceController
destination:(id)destinationController
and in Swift:
init(identifier identifier: String,
source sourceController: AnyObject,
destination destinationController: AnyObject)
Note the non-optional requirement in the Swift initialiser. Therein lies the problem and the crash. If you don't deliberately set an identifier in the storyboard, the Custom segue's designated initialiser will be called using a nil value for the identifier. Not a problem in Objective-C, but bad news for Swift.
The quick solution is to ensure you set an identifier string in Storyboard. For a more robust solution, it turns out that you can override the designated initialiser in your custom subclass to intercept a nil-valued string. Then you can fill it in with a default value before passing on to super's designated initialiser:
override init(identifier: String?,
source sourceController: AnyObject,
destination destinationController: AnyObject) {
var myIdentifier : String
if identifier == nil {
myIdentifier = ""
} else {
myIdentifier = identifier!
}
super.init(identifier: myIdentifier,
source: sourceController,
destination: destinationController)
}
I have updated the sample project to reflect this solution
The same issue comes to me since I forgot make Identity to the segue.
After that, my segue subclass could worked fine.
Highly recommend you take a look at the Apple documentation. If you dig into it a bit, you'll notice in the perform method, you can override animations and such:
SWIFT
func perform()
OBJECTIVE-C
- (void)perform
"You can override this method in your NSStoryboardSegue subclass to perform custom animation between the starting/containing controller and the ending/contained controller for a storyboard segue. Typically, you would use Core Animation to set up an animation from one set of views to the next. For more complex animations, you might take a snapshot image of the two view hierarchies and manipulate the images instead of the view objects.*
Regardless of how you perform the animation, you are responsible for installing the destination view controller o window controller (and its contained views) in the right place so that it can handle events. Typically, this entails calling one of the presentation methods in the NSViewController class."
What you might do as well is have a look at some of the iOS UIStoryboardSegue examples out there in the wild and you should find they're quite similar.

How do I deselect all table rows in NSOutlineView when clicking in the empty space of the view?

For example when I click on the red dot below:
I want the following deselection to occur:
I set up the view-based NSOutlineView using bindings for both the data source and the selection indexes. So far i've tried to override the TableCellView becomeFirstResponder and also override NSOutlineView's becomeFirstResponder however it seems NSOutlineView never actually gives up first responder status?
Some advice would be very much appreciated!
I found this post on the topic. The solution appears to be in creating a subclass of NSOutlineView and overriding mouseDown: so that you can determine whether the click was on a row or not. When the click is on a row you just dispatch to super. If it's not you send deselectAll: to your NSOutlineView.
I haven't tried it myself but there are various posts around which come up with comparable code.
Use setAction: method of NSOutlineView.
[mOutlineView setAction:#selector(doClick:)];
[mOutlineView setTarget:self];
-(IBAction) doClick:(id)sender;
{
if ([mOutlineView clickedRow] == -1) {
[mOutlineView deselectAll:nil];
}
}
Swift 5. in NSOutlineViewDelegate
func outlineViewSelectionDidChange(_ notification: Notification) {
//1
guard let outlineView = notification.object as? NSOutlineView else {
return
}
outlineView.deselectAll(nil)
}