UIView cropped off even though constraints are set - swift

I've created a custom view in a xib file, in which I've set all simulated Metrics to "Inferred"
In my view I float one stack view x to the left by pinning the left and the top side and stack view to the right by pinning the right and top. Somehow like this (the = sign symbolizes the screen borders)
==========
=x y=
= =
= =
= =
= =
==========
I don't set any constraints for the width and the height, since everything should be inferred.
The corresponding class to my view is quite simple:
class MyView: UIView {
var view:UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
view = UIView.loadFromNibNamed("MyView")
addSubview(view)
}
}
extension UIView {
class func loadFromNibNamed(nibNamed: String, bundle : NSBundle? = nil) -> UIView? {
return UINib(
nibName: nibNamed,
bundle: bundle
).instantiateWithOwner(nil, options: nil)[0] as? UIView
}
}
I use this view in my storyboard by pinning it to the left, right, top and bottom.
.
The thing is that the view is loaded from the nib, but that the width is somehow not dynamic. I would expect to the view to resize itself depending on the size of the parent. However, approximately 1/3 of the right Stack View is cropped off and not visible on screen even though I've set the constraints.
What do I have to do to fix this?

You need to set the translatesAutoresizingMaskIntoConstraints property to false for any view you load from a XIB if you want it to have a dynamic frame size. Otherwise the system will automatically create layout constraints like a fixed width and a fixed height that will keep your view from resizing.
Additionally, you need to add some constraints in code after adding your view to the view hierarchy that pin its top, bottom, left and right edge to the corresponding edges of its superview. After all, you're adding the XIB's contents as a subview to your custom view MyView i.e. the two views are not the same and you need to tell the system how it should position the subview (the constraints you added in your storyboard only relate to your MyView instance, not to its subview). These additions to your code should do the trick:
class MyView: UIView {
var view:UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
view = UIView.loadFromNibNamed("MyView")
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
// Pin view to all four edges of its superview
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: [], metrics: nil, views: ["view": view]))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: [], metrics: nil, views: ["view": view]))
}
}
Side notes:
Read a more detailed explanation on the translatesAutoresizingMaskIntoConstraints property in the official documentation.
All settings in the "simulated metrics" section in Interface Builder only apply to Interface Builder itself. They won't have any effect on your app when you run it on Simulator or a real device.

We need to identify were the issue is, so let's start by confirming that the stack view's are getting their frame's set. In your view controller where the stack views are we can override the following methods and check the frames of the stack views.
viewWillLayoyutSubviews()
viewDidLayoutSubviews()
Confirm that the frame's are getting set we can now look at the constraints that are being set in the storyboard. It is not clean fro the information you have provided how you want the stack views layout together, is one on top of the other view? Are they supposed to be side-by-side?
You will need to check that the constraints are valid for the layout you you want.
3.Confirm the Size Classes for your view(s) are configured correctly. Apple has a document called 'Size Classes Design Help' here. I believe this is your problem. Your custom class is set to have a size class of Any/Any and the super view of the custom view has a different set of size classes, for example Compact/Regular. So you custom view is not adjusting it's size based on size classes since it is set to be the same for any combination of size classes. Try configuring a different size class combination for your custom class to to see if there are any differences - Compact/Regular for example.

Related

Why the view does not fit on the parent view?

I can't understand that the view does not fit on the parent view ? This screenshot shows problem with green button that button doesn't fit parent view. Red background color it is containerView.
I'm using SnapKit for constraints. Please help me. Thanks!
Screenshot of the result
private lazy var scrollView = UIScrollView()
private lazy var containerView = UIView()
// etc
// viewDidLoad
scrollView.addSubview(containerView)
containerView.addSubviews([boxView, addButton])
boxView.addSubviews([titleLabelView, vStackView])
view.addSubview(scrollView)
// viewWillLayoutSubviews
scrollView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
containerView.snp.makeConstraints {
$0.edges.equalToSuperview()
$0.width.equalToSuperview()
}
addButton.snp.makeConstraints {
$0.height.equalTo(65)
$0.top.equalTo(boxView.snp.bottom).offset(24)
$0.leading.trailing.equalToSuperview().inset(40)
}
Without the makeConstraints() method can't be certain, but if it's just applying normal autolayout constraints you will need to use a negative value for the bottom constant.
In AL a positive value means move down from the anchor, whereas you need to move the edge of the button up from the anchor (the box's bottomAnchor).
If you've come from Interface BUilder this isn't obvious as in IB the negative values are applied by tool.
$0.top.equalTo(boxView.snp.bottom).offset(-24)

NSScrollView not scrolling

I have a form in a Mac app that needs to scroll. I have a scrollView embedded in a ViewController. I have the scrollView assigned with an identifier that links it to its own NSScrollView file. The constraints are set to the top, right, and left of the view controller, it also has the hight constraint set to the full height of the ViewController.
Here is my code:
import Cocoa
class ScrollView: NSScrollView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
NSRect documentView.NSMakeSize(0, 0, 1058.width, 1232.height)
}
override func scrollWheel(with event: NSEvent) {
switch event.phase {
case NSEvent.Phase.began:
Swift.print("Began")
// case NSEvent.Phase.changed:
// Swift.print("Changed")
case NSEvent.Phase.ended:
Swift.print("Ended")
default:
break
}
switch event.momentumPhase {
case NSEvent.Phase.began:
Swift.print("Momentum Began")
// case NSEvent.Phase.changed:
// Swift.print("Momentum Changed")
case NSEvent.Phase.ended:
Swift.print("Momentum Ended")
default:
break
}
super.scrollWheel(with: event)
}
I cant seem to get my app to scroll at all. I think I am not setting the frame correctly. What is the best way to do set the frame correctly? Am I coding the NSScrollView correctly?
I think you are making your life very hard because you are doing things that are not exactly recommended by Apple. First of all, you should not subclass NSScrollView. Rather you should read first Introduction to Scroll View Programming Guide for Cocoa by Apple to understand how you should create the correct hierarchy of views for an NSScrollView to work correctly.
A second recommendation is for you to check this nice article about how you should set up an NSScrollView in a playground, so that you can play with the code you want to implement.
Third, using Autolayout and NSScrollView has caused a lot of grief to a lot of people. You need to set up the AutoLayout just right, so that everything is going to work as expected. I recommend that you check this answer by Ken Thomases, which clearly explains how you need to set up auto layout constraints for an NSScrollView to work properly.
I just got over the "hump" with a NSScrollView inside a NSWindow. In order for scrolling to occur the view inside the NSScrollview needs to be larger than the content window. That's hard to set with dynamic constraints. Statically setting the inner view to a larger width/height than the window "works" but the static sizes usually are not what you want.
Here is my interface builder view hierarchy and constraints, not including the programmatically added boxes
In my app the user is adding "boxes" (custom draggable views) inside the mainView, which is inside a scrollview in a NSwindow.
Here's the functionality I wanted:
If I expanded the NSWindow, I wanted the mainView inside the scrollview to expand to fill the whole window. No scrolling needed in this case if all the boxes are visible.
If I shrank the NSWindow, I wanted the mainView inside the scrollview to shrink just enough to include all my mainView subviews ("boxes"), but not any further (i added a minBorder of 20). This results in scrolling if a box's position is further right/up than the nswindow's width/height.
I found the trick is to calculate the size of the mainView I want based on the max corner of each draggable boxview, or the height/width of the content frame of the nswindow, whichever is larger.
Below is my code, including some debugging prints.
Be careful of which subviews you use to calculate the max size. If you include a subview that's dynamically attached to the right/top of the window, then your window will never shrink. If you add +20 border to that, you might infinite loop. Not a problem in my case.
extension MapWindowController: NSWindowDelegate {
func windowDidEndLiveResize(_ notification: Notification) {
if let frame = window?.frame, let content = window?.contentRect(forFrameRect: frame) {
print("window did resize \(frame)")
var maxX: CGFloat = content.width
var maxY: CGFloat = content.height
for view in mainView?.subviews ?? [] {
let frameMaxX = view.frame.maxX + minBorder
let frameMaxY = view.frame.maxY + minBorder
if frameMaxX > maxX {
maxX = frameMaxX
}
if frameMaxY > maxY {
maxY = frameMaxY
}
}
print("view maxX \(maxX) maxY \(maxY)")
print("window width \(content.width) height \(content.height)")
mainView?.setFrameSize(NSSize(width: maxX, height: maxY))
}
}
}

How to make a pop over view automatically adjust to its content size?

I subclassed a UIViewController to present it as a pop over on iPhone using its popoverPresentationController.
The view controller's view contains only a UILabel that is added using auto layout constraints.
The presented pop over is much larger than the label. I want the pop over to be only as large as necessary to fit its content. I tried by overriding preferredContentSize but that didn't work:
override var preferredContentSize: CGSize {
get {
label.sizeToFit()
view.sizeToFit()
return view.bounds.size
}
set {}
}
How can I make sure the pop over automatically resizes to its content?

How to handle iOS 11 large title animation when using multiple container views?

I am making an app at the moment where 1 screen has a segmented control with 3 segments. Initially I had 1 table view and when you change segment I would simply change the data source/cell etc and reload the table. While this works great there is always the problem that when you change segments it will not remember your last scroll position because the table view gets reloaded.
I tried to get around this with storing offset position, rows etc but I could never get it to work like I wanted. Seems especially annoying when you have different cell types for the segments and they are self sizing as well.
I than decided to have a master view controller with the segmented control and 3 container views with their own VC and table view for each segment. I simply hide/show the correct container view when changing segments. This also works great but I have 1 problem with iOS 11 style large headers. Only the 1st container view added as a subview to the ViewControllers view manipulates the collasping/expanding of the title when you scroll.
Therefore when I change to the 2nd or 3rd container view and start scrolling I do not get the large title collapsing animation. How can I get around that?
I tried the following
1) Change Container view zPosition when changing segments
2) Move the container view to the front by calling view.bringSubview(toFront: ...)
3) Looping through the subviews and calling
view.exchangeSubview(at: 0, withSubviewAt: ...)
I believe I could remove all container views and add the one I need again and give them constraints but I wonder if there is a more straight forward solution.
Or if someone has a good solution to remember a tableViews scroll position before reloading it I would appreciate that too.
So I found an answer that seems to work for me, thanks to this great article. https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/
Essentially what I did is
1) Remove the ContainerViews and Segues from the MasterViewController Storyboard.
2) Add a lazy property for each VC of the segmented control in the MasterViewController. They are lazy so that they only get initialised when you actually need them
lazy var viewController1: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
lazy var viewController2: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
3) Create an extension of UIViewController with the following 2 methods. I added them in an extension purely for code organisation as they might be reused on other ViewControllers.
extension UIViewController {
func add(asChildViewController viewController: UIViewController) {
// Add Child View Controller
addChildViewController(viewController)
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
func remove(asChildViewController viewController: UIViewController) {
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
}
4) Now in my segmented control method that gets called when you change segment I simply add the correct ViewController. The nice thing is that remove/adding them does not actually deallocate them.
func didPressSegmentedControl() {
if segmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: viewController2)
add(asChildViewController: viewController1)
} else {
remove(asChildViewController: viewController1)
add(asChildViewController: viewController2)
}
}
5) Make sure you call the method at point 4 in ViewDidLoad so that the correct VC is added when the VC is loaded the 1st time.
func viewDidLoad() {
super.viewDidLoad()
didPressSegmentedControl()
}
This way when we remove a ChildViewController and add another one it will always be the the top VC in the subviews array and I get my nice title collapsing animation.
Another added benefit of this approach is that if you never go to a particular segment that particular VC will never get initialised, because they are lazy properties, which should help with efficiency.
Hope this helps somebody trying to do the same.
This is a horrible issue which I hope will be resolved soon, but there is another fix - although I freely admit that this is a nasty hack.
Basically, the issue only applies to the FIRST container view in the hierarchy. Add another container view to your hierarchy. Set it as hidden, then delete its segue and its target view controller just to be neat. Finally, make sure that this is the first container view in the hierarchy by dragging it to the top of the list.

Make subview fit inside container and resize correctly

I'm trying to load dynamic nibs as subviews of containers. I almost got it to work, except that the subviews have an offset I can't seem to get rid off (cf pink view in pictures below).
From the View Hierarchy debugging:
As you can see in 2nd picture, the container frame is correctly positioned, whereas the subview isn't, for some reason.
I don't really know what is going with autolayout.
Here's the code that deals with loading the nib and assigning it as subview:
The commented-out code is all the things I've tried to make it work, with no success. I thought autolayout would work on its own without me having to do anything, but by default it loads the nib without resizing it.
That means the leading and top anchors are correct, however the nib then uses its full size... (cf picture below)
So the question is, what is needed for me to do in order to load the nib and make it fit to the container view ?
You should add constraints to your NibView instead of setting the bounds and the frame of the NibView.
Try to call the following function (addFullScreenConstraint) on the NibView after adding the NibView as a subview of the content view:
extension UIView {
/// Adds constraints to this `UIView` instances `superview` object
/// to make sure this always has the same size as the superview.
/// Please note that this has no effect if its `superview` is `nil`
/// – add this `UIView` instance as a subview before calling this.
func addFullScreenConstraints() {
guard let superview = self.superview else {
return
}
self.translatesAutoresizingMaskIntoConstraints = false
superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|",
options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|",
options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
}
}