How to set initial width of NSSearchToolbarItem - appkit

The default width of NSSearchToolbarItem is way too large. I want it to be similar to the Notes app.
Is there an official way to do this?
The control has a property preferredWidthForSearchField, but this has no effect, which could also be one of the many Big Sur bugs.
The classic but deprecated minSize and maxSize settings seems to work, but then the control loses the ability to automatically resize itself according the available toolbar space.

Maybe not the best way, but this seems to work:
searchToolbarItem.searchField.addConstraint(NSLayoutConstraint(item: searchToolbarItem.searchField, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: 200) )
searchToolbarItem.searchField.addConstraint(NSLayoutConstraint(item: searchToolbarItem.searchField, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: 30) )

Related

Different font output for same NSAttributedString with CATextLayer and NSTextView

In macOS using the same NSMutableAttributedString for a CATextLayer and a NSTextView seems to give different rendering results. The NSTextView has a slightly larger font than a CATextLayer. This behavior seems to occur with any type of font structure (NSFont, CTFontCreateWithName, etc) fed to the controls. Even not defining a font will cause this to happen when the controls default to the system font. Here's a distilled down snippet that will run as a Playground. It creates a CATextLayer on the left side and a NSTextView on the right side. Uses the exact same font and string. Anyone solved this one yet?
import Cocoa
let attributes = [NSAttributedString.Key.font: NSFont(name: "Helvetica", size: 23.0)!,
NSAttributedString.Key.foregroundColor: NSColor.gray]
let theString = NSMutableAttributedString(string: "The Quick Brown Fox Jumped Over The Lazy Dogs Back", attributes: attributes)
var parentView = NSView(frame: NSRect(origin: NSPoint(x: 0, y: 0), size: CGSize(width: 300, height: 300)))
// create CATextLayer - left side
var textLayer = CATextLayer()
textLayer.isWrapped = true
textLayer.contentsScale = NSScreen.main!.backingScaleFactor
textLayer.backgroundColor = CGColor.white
textLayer.foregroundColor = CGColor.black
textLayer.string = theString
var layerView = NSView(frame: NSRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 90, height: 300)))
layerView.wantsLayer = true
layerView.layer = textLayer
parentView.addSubview(layerView)
// create NSTextView - right side
var textView = NSTextView(frame: NSRect(origin: NSPoint(x: 100, y: 0), size: CGSize(width: 90, height: 300)))
textView.textStorage?.setAttributedString(theString as NSAttributedString)
parentView.addSubview(textView)
// final display
parentView
The final ruling is - NSTextView and CATextLayer are going to render differently. Here is what Apple says:
"This is an interesting question and I have visited this before with other developers. Yes, you are correct. The text rendering inside of CATextLayers is different than in an NSTextView. I have talked with engineering about these differences before and I do not think there is a solution to the problem you have posed using CATextLayer. Here is why:
Attributed strings drawn to a CATextLayer use different attributes than regular attributed strings. These are described here - https://developer.apple.com/documentation/coretext/styling_attributed_strings. There is some pairity with some of the regular attributed string attributes so you'll see some strings will seem to work, but some of the atrributes used by regular attributed strings are not supported. For example, strike-through text is not supported.
CATextLayer also has a different concept of sizing and spacing than when drawing to regular layers. This is undocumented and I do not know how to reconcile these difference with NSTextView output.
So, in summary, yes, for the reasons mentioned above, I expect that sometimes whatever you are drawing to CATextLayer will look different than what you are seeing drawn inside of an NSTextView. These differences are not documented and we do not have a forumula to map between text drawn in CATextLayer and NSTextView. I recommend using a layer-backed view instead."

Handling AutoLayout constraint animation differences in iOS 10?

I've noticed that in iOS 10 Beta 5 (about to try Beta 6), AutoLayout constraint animation behaves a bit differently.
For example, this approach does not work the same as it did in previous iOS releases:
[view addConstraints:#[constraints...]];
[view setNeedsUpdateConstraints];
[view layoutIfNeeded];
[UIView animateWithDuration:...
{
/* adjust constraint here... */
[view layoutIfNeeded]; // Only the adjusted constraints since previous layoutIfNeeded() call should animate change with duration.
} completion:{ ... }];
... In my testing, the constraints initially added with addConstraints() will also animate in iOS 10 with the UIView animateWithDuration() block... which is causing some funky/undesirable behavior so far.
For example, setting the left/right constraints in the array (but the vertical constraints in the block) causes the entire view to animate onto the screen diagonally with this approach... which is totally incorrect.
Does anyone know how to do this correctly for both iOS 9 (and below), as well as 10?
Try calling layoutIfNeeded on the view's superview, instead of the view itself.
--
I had a similar problem, my view was supposed to animate from the top at a 0 height, downward to a > 0 height (i.e. (0, 0, 320, 0) to (0, 0, 320, 44)).
Using Xcode 8 with iOS 10, the animation behavior was much different. Instead of animating downward from the top, the view animates up & down from the vertical center of the destination frame.
I fixed this by, instead of calling layoutIfNeeded on the view itself, calling it on the view's superview. Somehow, the behavior was restored.
Hope it helps!
Helpful comment by #Ramiro:
According to the documentation, layoutIfNeeded lays out the subviews (but doesn't contemplate the current view itself). So, in order to update the current view lay out, you need to call layoutIfNeeded from the super view.
In below method :
[super viewDidLoad];
Write this line:
[self.view layoutIfNeeded];
I made a small test changing H and V of a small red view:
self.red_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-90-[redView]", options: defaultOptions, metrics: nil, views: viewsDictionary)
self.red_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:|-30-[redView]", options: defaultOptions, metrics: nil, views: viewsDictionary)
self.view.addConstraints(self.red_POS_V!)
self.view.addConstraints(self.red_POS_H!)
and animated it:
// we hope is only one:
let currV = self.red_POS_V![0]
let currH = self.red_POS_H![0]
UIView.animate(withDuration: 3) {
// Make all constraint changes here
currV.constant = 100
currH.constant = 300
self.view.layoutIfNeeded() // Forces the layout of the subtree animation block and then captures all of the frame changes
}
red view moved correctly, if You comment out single line in animation, it does work, horizontally or vertically.
Small project avalable, if You need it.

Why is my layout constraint returning an error in this case? (Swift)

I am trying to peg a label (progressTimer label) 10 units to the left of my slider (sliderDemo). I am using the following constraint but for some reason my application keeps on crashing. I can't seem to find anything wrong with it. Any help would be greatly appreciated!
var constS1 = NSLayoutConstraint(item: progressTimerLabel, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: sliderDemo, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 10)
progressTimerLabel.addConstraint(constS1)
Here is part of the error log
The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x79edb790 UILabel:0x79e74dd0'0:00'.right == UISlider:0x79e744f0.left + 10>
The reason why the first version was not working is that you were adding the constraints to the wrong view.
It works like this:
If you have a width or height constraint, you can add it to the view itself and it will work
If you have constraints that define all the other attributes of the view, you need to add your constraints to the superview. This is the reason why the second option worked, because sliderView is the superview for the label and the slider.
Just in case you have the error in the future, so you know why it was not working :)
Apparently this worked:
var constS1 = NSLayoutConstraint(item: progressTimerLabel, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: sliderDemo, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 10)
sliderView.addConstraint(constS1)
😊 Hope this helps!

Achieving this layout for a viewcontroller using autolayout

I am working on a viewcontroller and I would like to try to achieve something like the picture below. I'd like to do this so it looks great on any device with regards to aspect ratio.
The top is a container, the middle is a collectionview, and the bottom is a uitableview.
What i'm trying to preserve is the aspect ratios. My thought to do this was the following:
For the first box, set the leading, trailing, and top margins to be to the container (guideline). Set the bottom one to be the box below (the larger middle box). Set the aspect ratio as well.
For the middle box, set the leading/trailing margins to the guidelines, and set the bottom to the box below. Also set the aspect ratio.
For the last box, set the leading, trailing, bottom (to the guideline) and also the aspect ratio.
I also set to pin widths equally
After doing this, it preserves my ratios correctly but it throws a ton of errors and warnings. Any ideas as to why this would be cranky at me? The crashing/warning report:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x7f8a66031bc0 V:[UITableView:0x7f8a65837c00(73)]>",
"<NSLayoutConstraint:0x7f8a6605c150 UITableView:0x7f8a65837c00.width == 7.78082*UITableView:0x7f8a65837c00.height>",
"<NSLayoutConstraint:0x7f8a6604e970 UICollectionView:0x7f8a65838400.leading == UIView:0x7f8a66031eb0.leadingMargin>",
"<NSLayoutConstraint:0x7f8a6604e9c0 UICollectionView:0x7f8a65838400.trailing == UIView:0x7f8a66031eb0.trailingMargin>",
"<NSLayoutConstraint:0x7f8a6604ea10 UICollectionView:0x7f8a65838400.width == UITableView:0x7f8a65837c00.width>",
"<NSLayoutConstraint:0x7f8a63c4ccf0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7f8a66031eb0(320)]>"
)
Thanks so much!
Say, you want top view's height to be 20% of the main view and middle view's height to be 50% of the main view. You can do this programatically like this:
[topView setTranslatesAutoresizingMaskIntoConstraints: NO];
[middleView setTranslatesAutoresizingMaskIntoConstraints: NO];
[bottomView setTranslatesAutoresizingMaskIntoConstraints: NO];
NSDictionary *views = #{#"topView": topView, #"middleView": middleView, #"bottomView": bottomView};
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|[topView]|" options: 0 metrics: nil views: views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|[middleView]|" options: 0 metrics: nil views: views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|[bottomView]|" options: 0 metrics: nil views: views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"V:|[topView][middleView][bottomView]|" options: 0 metrics: nil views: views]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem: topView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: self.view attribute: NSLayoutAttributeHeight multiplier: 0.2f constant: 0.0f]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem: middleView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: self.view attribute: NSLayoutAttributeHeight multiplier: 0.5f constant: 0.0f]];
You need not set aspect height for the bottom view. You only need to pin the bottom view with the bottom edge of the main view.
If you want to do in Interface Builder, you can do this way:
For the top box, add 'Leading', 'Trailing' and 'Top' constraints to the superview. Also, add 'Equal Heights' constraint to the superview and modify the multiplier to the required value (Refer the last image).
For the middle box, add 'Leading' and 'Trailing' constraints to the superview. Add 'Top' constraint to the top box. Also, add 'Equal Heights' constraint to the superview and modify the multiplier to the required value.
For the last box, add 'Leading', 'Trailing' and 'Bottom' constraints to the superview. Add 'Top' constraint to the middle box.

difference between CGRectMake and setContentSize scrollVew

what is the difference between this two instruction
scrollView.frame=CGRectMake(0, -100, 960, 100);
[scrollView setContentSize:CGSizeMake(960, 100)];
scrollView.frame=CGRectMake(0, -100, 960, 100);
Sets the frame of the UIScrollView. Which means you set the origin and the visible size of UiScrollView on your view.
[scrollView setContentSize:CGSizeMake(960, 100)];
Sets the Content size of your UIScrollView. You use a UIScrollView, because you have more content to display than you can show within the frame of your view. Therefore you have to set the size of the contentView, which you scroll up and down.
Take a look at the Apple docs:Creating and Configuring Scroll Views

Resources