Why doesn't overriding `intrinsicContentSize` in `NSView` work for `NSScrollView`? - nsview

I have placed an instance of custom class BigView that is a subclass of NSView inside a NSScrollView in IB. The content size of my BigView will be computed at runtime. What is the best practices for setting the content size?
Overriding intrinsicContentSize, as suggested in the The Big Nerd Ranch guide, does not seem to work -- the frame remains at its original size:
class BigView: NSView {
...
override var intrinsicContentSize: NSSize { // doesn't work?!
return NSSize(width: 10000, height: 10000)
}
...
}
Setting the frame programmatically (or in IB) does work:
class BigView: NSView {
...
override func awakeFromNib() {
...
self.frame = NSRect(x: 0, y: 0, width: 10000, height: 10000)
}
...
}I
or from the controller:
class ViewController: NSViewController {
#IBOutlet weak var bigView: BigView!
...
override func viewDidLoad() {
super.viewDidLoad()
bigView.frame = NSRect(x: 0, y: 0, width: 1000, height: 1000)
}
...
}
This can also be done throughout the scrollview's documentView property:
class ViewController: NSViewController {
#IBOutlet weak var scrollView: NSScrollView!
...
override func viewDidLoad() {
super.viewDidLoad()
scrollView.documentView?.frame = NSRect(x: 0, y: 0,
width: 1000, height: 1000)
}
...
}
I suppose one could also use AutoLayout constraints as well.
What doesn't overriding intrinsicContentSize work?

The problem is that when translatesAutoresizingMaskIntoConstraints is enabled in newer versions of AppKit default constraints are set without considering the intrinsic size.
To fix this you need to add the constraints manually. First pin the position of your BigView to the top left of the superview (Should be the NSClipView). Then go to the Size Inspector, and in the Content Compression Resistance Priority section set Intrinsic Size to Placeholder. That gives your view the constraints it needs and everything should work at runtime.

Related

How do you enlarge the search icon on an NSSearchField?

I'm making a search field with a larger height than the default. Although its text size can be increased to the proper size, the icon stays the same size:
I tried to fix this by overriding NSSearchField's rectForSearchButton(whenCentered:)'s default rectangle. However, not only did it not increase the size of the button, but it also duplicated the button image.
override func rectForSearchButton(whenCentered isCentered: Bool) -> NSRect {
return NSRect(x: 10, y: 10, width: 30, height: 30)
}
I then tried commenting out the above and subclassing NSSearchFieldCell, which had a similar result.
override func searchButtonRect(forBounds rect: NSRect) -> NSRect {
return NSRect(x: 10, y: 10, width: 30, height: 30)
}
How can I properly center and enlarge this icon? I want it to fill up the whole space and be centered, or at least look like a normal search field.
You need to change button image scaling. Assuming your custom class is instantiated from Storyboard/XIB and the height is modified via constraint, it could be like below:
class CustomSearchFiled: NSSearchField {
override func awakeFromNib() {
if let cell = self.cell as? NSSearchFieldCell {
cell.searchButtonCell?.imageScaling = .scaleProportionallyUpOrDown
}
}
...

Image Slider using UIScrollView and Auto Layout

I am trying to implement a image slider using scrollView and pageControl, with the images being appended to the scrollView programmatically using the .addSubView method. The code is as follows:
#IBOutlet weak var sliderScrollView: UIScrollView!
#IBOutlet weak var sliderPageControl: UIPageControl!
var images: [String] = ["0", "1", "2"]
func updateSlider() {
sliderPageControl.numberOfPages = images.count
for index in 0..<images.count {
frame.origin.x = sliderScrollView.frame.size.width * CGFloat(index)
print(sliderScrollView.frame.size.width)
frame.size = sliderScrollView.frame.size
let image = UIImageView(frame: frame)
image.contentMode = .scaleAspectFill
image.image = UIImage(named: cafeObject.images[index])
sliderScrollView.addSubview(image)
}
sliderScrollView.contentSize = CGSize(width: sliderScrollView.frame.size.width * CGFloat(cafeObject.images.count), height: sliderScrollView.frame.size.height)
sliderScrollView.delegate = self
sliderPageControl.currentPage = 0
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
switch scrollView {
case sliderScrollView:
sliderPageControl.currentPage = Int(pageNumber)
default:
break
}
}
As I designed the storyboard on iPhone 8 layout, the above code works nicely for iPhone 8. However, once I run the code in iPhone 8 Plus, the photo does not adapt the new size of the scrollView. I have added constraints to the scrollView such that the top, leading, trailing and bottom are equal to the super view's top, leading, trailing and bottom. When debugging, I realized that the UIImageView's frame is still using the old width as on iPhone 8.
Any workaround for this either programatically or using interface builder? Thanks!
Okay I managed to get this fixed by placing the updateSlider() function under viewDidLayoutSubviews() instead!

Can't set var in subview

I have an app with a UIViewController with custom UIViews in it. I want to set a variable in that custom UIView and it doesn't update...
I have done this a thousand times and it always worked but this time it doesn't and I really can't figure out what I'm doing wrong, any help is welcome!
In my viewcontroller:
var sjablonview: sjablonPicker!
sjablonview = sjablonPicker(frame: CGRect(x: btnUndo.frame.origin.x, y: DrawingView.frame.origin.y,
width: (btnX.frame.origin.x + btnX.frame.size.width) - btnUndo.frame.origin.x, height: 250))
sjablonview.alpha = 1
sjablonview.mpUitklapping = 300
self.view.addSubview(sjablonview)
In my custom UIView:
class sjablonPicker: UIView {
var mpUitklapping = CGFloat(100)
override init(frame: CGRect){
super.init(frame: frame)
print(mpUitklapping) // --> output = 100 and not 300
...
You are printing from inside your initialiser. Your view is initialized before you change the value of mpUitklapping. Your initializer gets called from this line:
sjablonview = sjablonPicker(frame: CGRect(x: btnUndo.frame.origin.x, y: DrawingView.frame.origin.y, width: (btnX.frame.origin.x + btnX.frame.size.width) - btnUndo.frame.origin.x, height: 250))
The value gets changed, you just don't print the updated value.

UINavigationBar bar height is not changing

I have a custom UINavigationController which I Simply resize the height but I get no result:
actually my main code is this: ( this will get perfect height according tu device size )
class NavigationController1: UINavigationController {
let Theframe = UIScreen.main.bounds
override func awakeFromNib() {
self.navigationBar.frame = CGRect.init(x: self.navigationBar.frame.origin.x*Theframe.width/375, y: self.navigationBar.frame.origin.y*Theframe.height/667, width:self.navigationBar.frame.width*Theframe.width/375, height: self.navigationBar.frame.height*Theframe.height/667)
}
}
but the upper code for height isn't working neither.
I solved the issue by putting in viewWillLayoutSubviews()
final code:
import UIKit
class NavigationController1: UINavigationController {
let Theframe = UIScreen.main.bounds
override func viewWillLayoutSubviews() {
self.navigationBar.frame = CGRect.init(x: self.navigationBar.frame.origin.x, y: self.navigationBar.frame.origin.y, width:self.navigationBar.frame.width, height: 200)
}
}
It could be override and redrawn because of the sIze inspector. Put the same requirements there so you can ensure its not overriding the custom code.

Swift delegation and storyboard #IBDesignable issue

Below is just a test of delegation.
What I did was, 1) draw a rectangle, 2) set this rectangle's width of line with a delegate, 3) Hope the storyboard could update its display.
There are two questions:
The first is: If I use "testView.widthdelegate = ViewController()" rather than "testView.widthdelegate = self" , the "var widthValue: CGFloat? = widthdelegate?.trueWidth" will be nil, but it should be 50, what's different between "self" and "ViewController()"?
The second is: I still want to update the result of draw in storyboard, where you can see I did a SetNeedDisplay() but no use at all, how could I do it?
View
import UIKit
protocol widthDelegate: class {
var trueWidth: CGFloat { get }
}
#IBDesignable
class TestView: UIView {
weak var widthdelegate: widthDelegate?
override func drawRect(rect: CGRect) {
var widthValue: CGFloat? = widthdelegate?.trueWidth ?? 1.0
rectangle(widthRefer: widthValue!)
println("width in TestView is \(widthdelegate?.trueWidth)" )
}
func rectangle(#widthRefer: CGFloat) -> UIBezierPath{
var rect = UIBezierPath(rect: CGRect(x: bounds.width/2-50, y: bounds.height/2-50, width: 100, height: 100))
rect.lineWidth = widthRefer
rect.stroke()
return rect
}
}
Controller
import UIKit
class ViewController: UIViewController,widthDelegate {
var trueWidth: CGFloat = 50
#IBOutlet var testView: TestView!{
didSet{ //after the storyboard loaded.
// testView.widthdelegate = ViewController()
testView.widthdelegate = self
testView.setNeedsDisplay()
}
}
}
Answer 1:
self is the actual instance of the class the code is in (correct solution)
ViewController() creates a brand new instance of ViewController which is not identical with the instance created in IB (wrong solution)
Answer 2:
Never implement didSet for an IBOutlet because it's never called during initialization. Better use viewDidLoad() for settings
Some other notes:
Please consider the naming convention that class, protocol and enum names start with a capital letter.
The class constraint in the protocol declaration is not needed