Cocoa Swift: Subview not resizing with superview - swift

I'm adding a subview(NSView), here is my code:
override func viewDidAppear() {
self.view.needsDisplay = true
let newView = NSView()
newView.autoresizesSubviews = true
newView.frame = view.bounds
newView.wantsLayer = true
newView.layer?.backgroundColor = NSColor.green.cgColor
view.addSubview(newView)
}
And it works fine
But when I resize the window the subview is not resizing.
Any of you knows why or how can make the subview resize with superview?
I'll really appreciate your help

You set view.autoresizesSubviews to true, which tells view to resize each of its subviews. But you also have to specify how you want each subview to be resized. You do that by setting the subview's autoresizingMask. Since you want the subview's frame to continue to match the superview's bounds, you want the subview's width and height to be flexible, and you want its X and Y margins to be fixed (at zero). Thus:
override func viewDidAppear() {
self.view.needsDisplay = true
let newView = NSView()
// The following line had no effect on the layout of newView in view,
// so I have commented it out.
// newView.autoresizesSubviews = true
newView.frame = view.bounds
// The following line tells view to resize newView so that newView.frame
// stays equal to view.bounds.
newView.autoresizingMask = [.width, .height]
newView.wantsLayer = true
newView.layer?.backgroundColor = NSColor.green.cgColor
view.addSubview(newView)
}

I found a fix for this issue:
override func viewWillLayout() {
super.viewWillLayout()
newView.frame = view.bounds
}

Related

Table View layout not updating on device orientation change

So I have created a tableView and have made it's frame identical to the view's frame, so therefore it should be the same size of the phone screen. However when I change the device orientation to landscape in the simulator the table view keeps the same dimensions from portrait mode.
Here is my tableView code:
func setTableView() {
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.frame = view.frame
tableView.backgroundColor = UIColor.lightGray
tableView.delegate = self
tableView.dataSource = self
tableView.separatorColor = UIColor.lightGray
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
Here is the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
setTableView()
}
Here is the method where I detect orientation change:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
Here is what I get in the simulator. In landscape mode the table view is only half of the width of the view while it should always fill the whole screen.
Usually if you want the frame of a view to determine its anchors, you don't set tableView.translatesAutoresizingMaskIntoConstraints = false. Setting that flag to false will force it to rely on anchors as opposed to its own view frame to set its constraints.
You could try setting it to true, or you could try just constraining the table to the view like so:
self.view.addSubview(tableView)
tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
This will constrain it to the view frame. If you do it this way, you don't need to worry about setting tableView.frame = view.frame. Personally, I prefer this method over relying on view's frames.

Snapkit centerY constraint centers item above the center Y axis

I'm trying to make a custom UICollectionView cell class. The cell consists of a content view and a label. I want the label to be in the center of the view, horizontally and vertically, but instead the label is placed above the content view's center y axis.
I've made sure that the constraints are set, no other constraints are being set, and that the issue affects all views in the content view (I added another view and set its center Y axis as a test, and that also didn't work). I also set the content view and the label's background colors to be contrasting, and have confirmed that the label is not lying on the content view's center y anchor.
Here is how I set the consraints:
label.snp.makeConstraints{make in
make.centerX.centerY.equalToSuperview()
}
Here is what I get instead. Clearly the label is not centered vertically. You can see the blue UIView, which I added as a test, is also not centered vertically.
I used to add my constraints programmatically in this way
self.view.addSubview(image)
image.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
image.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
image.heightAnchor.constraint(equalToConstant: 30).isActive = true
image.widthAnchor.constraint(equalToConstant: 30).isActive = true
and my image is declarated in this way
let image: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(named: "ico_return")
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
Hope it helps
Can you try Following Code.
class FilterCollectionViewCell: UICollectionViewCell {
let labelTemp = UILabel()
override func awakeFromNib() {
labelTemp.backgroundColor = .white
labelTemp.textColor = .black
labelTemp.text = "testing"
self.contentView.addSubview(labelTemp)
labelTemp.snp.makeConstraints { (make) in
make.centerX.centerY.equalTo(self.contentView)
}
}
}
Fast and easy:
myLabel.snp.makeConstraints { (make) in
make.center.equalTo(self.topView.snp.center)
}

How to stretch title view in swift?

I created custom title view for UINavigationItem.
And I want custom view stretch to right padding.
What can I do??
I tried to set rightBarButtonItem to nil.
But it seems no solution.
And don't stretch to left backButton area.
override func viewDidLoad() {
super.viewDidLoad()
let searchBar = CustomSearchBar()
searchBar.autoresizingMask = .flexibleWidth
searchBar.frame = CGRect(x: 0, y: 0, width: 400, height: 30)
self.navigationItem.titleView = searchBar
self.navigationItem.rightBarButtonItem = UIBarButtonItem()
}
See my result. I want a red area.
https://imgur.com/a/dar3cMN
Try the following inside your CustomSearchBar class:
override var intrinsicContentSize: CGSize {
return UILayoutFittingExpandedSize
}
let searchBar = CustomSearchBar()
searchBar.translatesAutoresizingMaskIntoConstraints = false
self.navigationItem.titleView = searchBar
searchBar.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
searchBar.rightAnchor.constraint(equalTo: self.navigationController!.navigationBar.rightAnchor).isActive = true
searchBar.leftAnchor.constraint(equalTo: self.navigationController!.navigationBar.centerXAnchor).isActive = true
The above code worked for me. You can remove the left anchor if you do not need the bar in the center. I also set the translatesAutoresizingMaskIntoConstraints to false, I hope this works.

UIScrollView Item not moving from the background

I setup my constraints so that I would have a chart that takes up the screen when you load in, but you are able to scroll down to see more information. However, I can see the scroll bar moving, and the chart does not move!
var scrollView: UIScrollView {
let scroll = UIScrollView()
self.view.addSubview(scroll)
scroll.snp.makeConstraints { (make) in
make.edges.equalTo(self.view)
}
return scroll
}
var contentView: UIView {
let content = UIView()
self.scrollView.addSubview(content)
content.snp.makeConstraints { (make) in
make.top.bottom.equalTo(self.scrollView)
make.left.right.equalTo(self.view)
make.width.equalTo(self.scrollView)
}
// self.scrollView.contentSize = content.frame.size
return content
}
override func viewDidLoad() {
super.viewDidLoad()
self.contentView.addSubview(self.chart)
self.chart.snp.makeConstraints { (make) in
// http://snapkit.io/docs/
// https://github.com/SnapKit/SnapKit/issues/448
make.top.equalTo(self.contentView)
make.left.right.equalTo(self.contentView)
make.height.equalTo(self.view).offset(-(self.tabBarController?.tabBar.frame.height)!)
}
self.scrollView.contentSize = CGSize(width: self.view.frame.width, height: 3000) // Testing
}
So the scroll bar moves, but the content is not.
Unfortunately, you're doing a number of things wrong.
First, when you use this construct:
var scrollView: UIScrollView {
let scroll = UIScrollView()
self.view.addSubview(scroll)
scroll.snp.makeConstraints { (make) in
make.edges.equalTo(self.view)
}
return scroll
}
Every time you refer to scrollView you are creating another instance of the scroll view. Running your code, and using Debug View Hierarchy, this is what you get:
The red views are scrollViews and the blue views are contentViews. As you can see, that's definitely not what you want.
Here is an example of how you can do what you're trying to do:
class ViewController: UIViewController {
var scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.backgroundColor = .red
return scroll
}()
var contentView: UIView = {
let content = UIView()
content.backgroundColor = .blue
return content
}()
var chart: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "This is the\nChart View"
v.backgroundColor = .cyan
v.textAlignment = .center
return v
}()
var bottomLabel: UILabel = {
let v = UILabel()
v.text = "The Bottom Label"
v.backgroundColor = .yellow
v.textAlignment = .center
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add scrollView
self.view.addSubview(scrollView)
// constrain to safe area (so it doesn't extend below the tab bar)
scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(self.view.safeAreaLayoutGuide)
}
// add contentView to scrollView
self.scrollView.addSubview(contentView)
// constrain all 4 edges to scrollView
// this will allow auto-layout to control / define the .contentSize
contentView.snp.makeConstraints { (make) in
make.edges.equalTo(self.scrollView)
}
// constrain contentView's width to scrollView's width
// but we *don't* constrain its height
contentView.snp.makeConstraints { (make) in
make.width.equalTo(self.scrollView)
}
// add the chart to contentView
self.contentView.addSubview(self.chart)
// constrain chart to top, left and right of contentView
// constrain its height to scrollView's height (so it initially fills the visible area
chart.snp.makeConstraints { (make) in
make.top.equalTo(self.contentView)
make.left.right.equalTo(self.contentView)
make.height.equalTo(self.scrollView)
}
// now, we'll add a label to scrollView
self.contentView.addSubview(self.bottomLabel)
// constrain the label left and right with 20-pts, and a height of 40
bottomLabel.snp.makeConstraints { (make) in
make.left.equalTo(self.contentView).offset(20)
make.right.equalTo(self.contentView).offset(-20)
make.height.equalTo(40)
}
// constrain the label's TOP 20-pts below the BOTTOM of chart view
bottomLabel.snp.makeConstraints { (make) in
make.top.equalTo(self.chart.snp.bottom).offset(20)
}
// constrain the BOTTOM of the label 20-pts from the bottom of contentView
bottomLabel.snp.makeConstraints { (make) in
make.bottom.equalTo(self.contentView).offset(-20)
}
// with those constraints, contentView's height is now:
// chart's height + 20 + label's height + 20
// and because contentView's bottom is constrained to scrollView's bottom,
// that becomes scrollView's contentSize
}
}
I added a UILabel as the chart view, and another label below it to show how to use auto-layout to define the scroll content.
Initial view:
Scrolled up:
and, the resulting View Hierarchy:
The comments I've included in the code should clarify how and why it's done this way.

How to make an Xcode playground rotate to landscape

I am trying to test a landscape view, but so far I cannot make progress. My code looks like this:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.text = "Hallo"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
self.view = view
NSLayoutConstraint.activate([label.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor)])
simulateDeviceRotation(toOrientation: .landscapeLeft)
}
override func viewDidAppear(_ animated: Bool) {
//simulateDeviceRotation(toOrientation: .landscapeLeft)
}
}
// Present the view controller in the Live View window
func simulateDeviceRotation(toOrientation orientation: UIDeviceOrientation) {
let orientationValue = NSNumber(value: orientation.rawValue)
UIDevice.current.setValue(orientationValue, forKey: "orientation")
}
PlaygroundPage.current.liveView = MyViewController()
When I uncomment the call to simulateDeviceRotation(toOrientation:) in loadView(), the result is:
Rotation in loadView()
And, when I uncomment simulateDeviceRotation(toOrientation:) in viewDidAppear(_:), the result is:
rotation in viewDidAppear(_:)
Of course, I would like to see the second result, but with the horizontal rotation of the first. Can you please point me in the right direction? I am missing something but I have not been able to finde it.
Don't mess with orientation, you won't succeed.
Change the last line of your code to:
var myViewController = MyViewController()
myViewController.preferredContentSize = CGSize(width: 668, height: 375)
PlaygroundPage.current.liveView = myViewController
and remove everything that handles with device orientation in loadView, viewDidAppear and remove method simulateDeviceRotation.
Update
If you set an arbitrary value as preferredContentSize, you will get into problems with constraints or worse like the view is displaced in the live view.
What I did: first read the default values of current content size:
print(myViewController.preferredContentSize)
This was 375.0 as width and 668.0 as height for me.
So just swap this values for the new preferredContentSize and everything should be fine.