Swift NSStackView : Learn By Doing. What am I doing wrong? - macos

How do I get my NSStackView to lay out my view one after the other ? My blue box is drawing on top of my red box.
import Cocoa
class TestView : NSView{
override init() {
super.init(frame: NSRect(x: 0,y: 0,width: 100,height: 100))
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.redColor().setFill()
NSBezierPath.fillRect(self.bounds)
}
}
class TestView2 : NSView{
override init() {
super.init(frame: NSRect(x: 0,y: 0,width: 100,height: 100))
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.blueColor().setFill()
NSBezierPath.fillRect(self.bounds)
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var searchFacetItems: SearchFacetSelectorView!
#IBOutlet weak var searchFacetHeader: SearchFacetSelectorHeader!
var content : NSStackView!
let testView = TestView()
let testView2 = TestView2()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
content = NSStackView(frame: window.contentView.bounds)
content.orientation = NSUserInterfaceLayoutOrientation.Vertical
content.alignment = NSLayoutAttribute.CenterX
content.addView(testView, inGravity: NSStackViewGravity.Center)
content.addView(testView2, inGravity: NSStackViewGravity.Center)
window.contentView = content!
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
learn by reading
It's not appkit, but maybe i'll get some clues: HOW TO USE UIVIEWS WITH AUTO LAYOUT PROGRAMMATICALLY
Auto Layout Guide

You should be able to do this by adjusting the NSRect coordinates. Currently you've got both NSRects using the same exact coordinates:
NSRect(x: 0, y: 0, width: 100, height: 100)
If you want the NSView horizontally (on the right) located next of the other:
NSRect(0, 0, 100, 100) // TestView1
NSRect(100, 0, 100, 100) // TestView2
vertically below:
NSRect(0, 0, 100, 100) // TestView1
NSRect(0, -100, 100, 100) // TestView2

The views don't have any constraints that describe their preferred sizes, such as intrinsicContentSizes or explicit constraints. And NSStackView only adds constraints that positions its stacked views relative to each other, not ones to size individual views.
Without them, their size in the stacking axis becomes ambiguous and will typically give all of the sizing to a single view. In your example, I'd guess that the blue box is not drawing on top of the red box, but just that the red box has a 0 height (and is stacked after the blue view).
Adding NSButtons to a stack view don't have this problem, as they do have a defined intrinsicContentSize.
Depending on what your views are -- do they have intrinsic sizes, or will they be defined by constraints to internal subviews -- you'll either want to override intrinsicContentSize or add those constraints that end up defining their heights.
Edit: Oh, and make sure translatesAutoresizingMaskIntoConstraints is set to false on the views you're adding to the stack view (it doesn't look like you are from the sample code). If not, you'll quickly run into trouble where the autoresizing constraints are trying to position the view and conflicting with constraints that the stack view adds.

Related

Why is NSComboBox content in the incorrect position?

I'm trying to layout an NSComboBox in a Mac app and I'm getting some weird behaviour. In reality I've got a view which has an NSComboBox as a subview along with some other subviews, but I've created a simple example to display the same issue I'm seeing.
For some reason, the text field isn't exactly vertically centered like I'd expect.
Here's my TestComboBox:
Here's an Apple NSComboBox (note the text is vertically centered and there's a small amount of leading spacing before the highlight colour):
I've created a simple example to show the issue:
class TestComboBox: NSView {
private let comboBox = NSComboBox(labelWithString: "")
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
comboBox.isEditable = true
addSubview(comboBox)
}
override func layout() {
comboBox.sizeToFit()
comboBox.frame = CGRect(x: 0, y: 0, width: bounds.width, height: comboBox.bounds.height)
}
}
If you add the above view to a basic Mac app (Storyboard) and pin it to the view controller with the following constraints:
I think I'm trying to layout the combo box correctly, but I'm not sure why it looks slightly different to the Apple example as they're both using NSComboBox!
Any guidance much appreciated!
The combo box is initialized with
private let comboBox = NSComboBox(labelWithString: "")
but init(labelWithString:) is a convenience initializer of NSTextField to create a label. Use init() instead:
private let comboBox = NSComboBox()

Making Buttons Circular in swift4

I have few buttons which are added from Storyboard(not by code) . I want to make it circular in shape . How do I do it? I don't want a circular button added from code but from storyboard.Thanks
You can make use of IBDesginable to make UIButton circular even in storyboard.
Add this class RoundButton in your project.
#IBDesignable class RoundButton : UIButton{
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func prepareForInterfaceBuilder() {
sharedInit()
}
func sharedInit() {
refreshCorners(value: cornerRadius)
}
func refreshCorners(value: CGFloat) {
layer.cornerRadius = value
}
#IBInspectable var cornerRadius: CGFloat = 15 {
didSet {
refreshCorners(value: cornerRadius)
}
}
}
Follow below steps to make it designable in Storyboard.
Add UIButton in viewcontroller and set some background color.
Set Width and Height same. Here both are 100.
Now you can see one more property named Corner Radius we declared in RoundButton class. Set it to 50 as half of Width/Height. You can set according to your need.
Now you can see rounded corner button within storyboard. You do not need to run code to see changes. You can make reuse of class in any other project to make button circular.
More about IBDesignable.
Very simple and easy.
yourButton.layer.cornerRadius = yourButton.frame.width/2
this will surely going to work.
Eat sleep code repeat😇
let button = UIButton(type: .custom)
button.frame = CGRect(x: 160, y: 100, width: 50, height: 50)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.clipsToBounds = true
button.setImage(UIImage(named:"thumbsUp.png"), for: .normal)
button.addTarget(self, action: #selector(thumbsUpButtonPressed), for: .touchUpInside)

Xcode 8 Playground doesn't display NSView

Xcode doesn't display NSView in the playground. But it's displaying UIView without any problem. Is it a bug?
Code:
let view = NSView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.layer?.backgroundColor = NSColor.white.cgColor
PlaygroundPage.current.liveView = view
Also the Xcode Playground is very slow. Is there any way to speed up the playground?
UIView and NSView both are different in their workings. The code you posted is enough for UIView but not for NSView.
According to Apple Documentation for NSView:
An NSView object provides the infrastructure for drawing, printing,
and handling events in an app. You typically don’t use NSView objects
directly. Instead, you use objects whose classes descend from NSView
or you subclass NSView yourself and override its methods to implement
the behavior you need.
and
draw(_:) draws the NSView object. (All subclasses must implement this
method, but it’s rarely invoked explicitly.)
So, you must inherit NSView and implement draw(_:)
Here is the code:
import Cocoa
import PlaygroundSupport
class view: NSView
{
override init(frame: NSRect)
{
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect)
{
NSColor.blue.setFill()
NSRectFill(self.bounds)
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
Output:
It is better to use iOS than macOS in Playground because it is easy and also you can find a ton of tutorials or answers.

Load multiple instances of a NSView from Nib

I was wondering if there was a method to create a view in a xib file and then connect its outlets to a class, so you can create multiple instances of that class and place them in the window.
I have some problems, so can you help me fix my code?
Here's what I did:
Firstly I created 2 files: CustomView.xib and CustomView.swift.
Then I designed the interface adding an NSImageView to the custom view. I set the file's owner to the class name and added an outlet from the NSImageView to the class.
Then I created the following function to load the interface from the nib:
func loadView() -> NSView {
var top = NSArray()
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: &top)
let view = top[0] as! NSView
return view
}
And set up the class to load the interface:
override init(frame: CGRect) {
super.init(frame: frame)
let view = loadView()
view.frame = bounds
addSubview(view)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
So I went on creating two variables of that class and placing it on the window:
var x = CustomView(frame: CGRect(x: 0, y: 0, width: 600, height: 105))
var y = CustomView(frame: CGRect(x: 0, y: 105, width: 600, height: 105))
And for some reasons this code gives me a strange error. It worked the first time, with one variable, but if I place more than one, it says it couldn't cast an NSWindow type in a NSView.
Could not cast value of type 'NSApplication' (0x7fffb5cf3ef0) to 'NSView' (0x7fffb5d04bb0).
I think that this error is given because sometimes the first top level object is the view, and sometimes it's the window. So I'm getting confused.
Obviously the error is thrown on this line:
let view = top[0] as! NSView
So what's the problem here?
(Please do not answer with cocoa touch code)
Use the filter function to get the NSView instance
func loadView() -> NSView {
var topLevelObjects = NSArray()
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as Array).filter { $0 is NSView }
return views[0] as! NSView
}

Custom TableViewCell with Subview whose height changes

I'm using a TableView and have a custom TableViewCell that I've added a subview to.
The problem is that I need the subview's height to change sometimes and therefore, the table's contentView would have to be updated as well as the row's height.
The subview of the custom TableViewCell is represented by the yellow background.
These images show what's currently happening in my simulator.
On Load
After the event that causes the subview's height to increase
What's the best approach to take with something like this?
Should I use constraints? And if so, what kind of constraints should I use? Would I have to then reload the tableview too every time the subview's size changes?
Here is the code I'm currently using for my custom TableViewCell:
import UIKit
class CustomCell: UITableViewCell {
var newView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.newView = UIView(frame: self.frame)
self.newView.backgroundColor = .yellowColor()
self.addSubview(newView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.newView.frame.size.width = self.frame.size.width // because self.frame.width is different than it was in the init method
}
func somethingHappenedThatMySubviewHasToIncreaseInHeight() {
self.newView.frame.size.height = self.frame.size.height + 40
}
}
The best approach is to use Auto Layout and self-sizing cells. Setup constraints in storyboard for your custom cell.
You will not need to reload the tableView. Each cell will automatically adjust its height, based on how much vertical space its subview takes.
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.

Resources