Collection view not obeying constraints in Xcode - xcode

I have a collection view that has its edges constrained to the safe area. However, the collection view fully covers the entire screen. Now if I change the contstraints remove the top constrain and change it to a fixed height constraint, the collection view STILL ignores it and fills the whole screen. When i remove all constraints, the collection view does not fill the whole screen. This error only affects the top constraint, all other constraints work fine.
UPDATE:
when I comment out some lines in the headerView supplementary view delegate function, it works again:
func pressedColour(colour: UIColor){
let origImage = self.imageView?.image
let tintedImage = origImage?.withRenderingMode(.alwaysTemplate)
self.setImage(tintedImage, for: .highlighted)
//self.imageView?.contentMode = .scaleAspectFill
self.tintColor = colour
}
The lines I comment out call that function. The autolayout error is
2018-09-13 23:38:16.556318+0100 DoppelChat[59765:15398941] [LayoutConstraints] 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.
(
"<NSLayoutConstraint:0x60c00028a780 UICollectionView:0x7fac548e6600.top == UILayoutGuide:0x60c0001bea00'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x60c0004892e0 h=-&- v=-&- 'UIView-Encapsulated-Layout-Top' UIView:0x7fac59a7e150.minY == 0 (active, names: '|':_UIParallaxDimmingView:0x7fac59a7eb90 )>",
"<NSLayoutConstraint:0x60c00048b4a0 'UIViewSafeAreaLayoutGuide-top' V:|-(64)-[UILayoutGuide:0x60c0001bea00'UIViewSafeAreaLayoutGuide'] (active, names: '|':UIView:0x7fac59a7e150 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60c00028a780 UICollectionView:0x7fac548e6600.top == UILayoutGuide:0x60c0001bea00'UIViewSafeAreaLayoutGuide'.top (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
I have narrowed it down to this line causing the issue:
let origImage = self.imageView?.image

ok, well I solved it, if it means anything to you, I used this function for button pressed colour instead:
func pressedColour(colour: UIColor, uiImage: UIImage?, alphaMultiplier: CGFloat){
let origImage = uiImage
let tintedImage = origImage?.withRenderingMode(.alwaysTemplate)
self.setImage(tintedImage, for: .highlighted)
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
colour.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
self.tintColor = UIColor(red: red, green: green, blue: blue, alpha: alpha * alphaMultiplier)
}
and the uiImage argument i passed in was the image literal not the button.imageview.image

Related

Horizontal scrollView/UIImageView layout issue

The goal: Have a scroll view that displays an array of uiimageviews (photos) that you can horizontally scroll through them
How I understand to do this: Make the frame (CGRect) of each uiimageview the height and width of the scroll view, the y value to 0 on each, and set the first imgViews x value to 0. For every imgView after that, add the width of the scrollview to the x value. In theory, this would line the imgViews (Photos) up next to each other horizontally and not allow for any vertical scrolling or zooming, purely a horizontal photo viewer.
The storyboard setup: I am creating my scrollview in a xib file (It’s a custom uiCollectionViewCell), with these constraints:
— Top space to cell (0)
— Trailing space to cell (0)
— Leading space to cell (0)
— Height of 400
— Bottom space to a view (0)
—— (See Below for img)
Laying out the UIImgViews:
func layoutScrollView() {
for (index, img) in currentImages.enumerate() {
let imgView = UIImageView(frame: CGRect(x: CGFloat(index) * scrollView.bounds.width, y: CGFloat(0), width: scrollView.bounds.width, height: scrollView.bounds.height))
imgView.contentMode = .ScaleAspectFill
imgView.image = img
scrollView.addSubview(imgView)
scrollView.contentSize = CGSize(width: imgView.frame.width * CGFloat(index), height: scrollView.bounds.height)
scrollView.setNeedsLayout()
}
}
My suspicion: I suspect the issue is stemming from the auto layout constraints i’ve specified, but (considering Im asking a SO question) not sure
If there is a better way to do this (really the correct way) please let me know! I have been trying to wrap my head around this for a few days now.
I appreciate all responses! Thanks for reading
EDIT #1
I tried paulvs approach of setting setNeedsLayout & layoutIfNeeded before the "for" loop, and still no luck. Here is (out of three images selected) the second photo displaying. It seems that both the first and second photos are way longer than the content view and that would move the middle view over (Squished).
Your code looks fine except for a few details (that may be causing the problem):
Add:
view.setNeedsLayout()
view.layoutIfNeeded()
before accessing the scrollView's frame (a good place would be before the for-loop).
This is because when using Autolayout, if you access a view's frame before the layout engine has performed a pass, you will get incorrect frames sizes/positions.
Remove these lines from inside the for-loop:
scrollView.contentSize = CGSize(width: imgView.frame.width * CGFloat(index), height: scrollView.bounds.height)
scrollView.setNeedsLayout()
and place this line after (outside) the for loop:
scrollView.contentSize = CGSize(width: imgView.frame.width * CGFloat(currentImages.count), height: scrollView.bounds.height)

Background image view behind a uitableview

Is it possible to add an imageView behind a UITableView? How would I add this image:
So that users could see it behind a tableview (background set to clear)?
The following should work:
tableView.backgroundColor = UIColor(patternImage: UIImage(named: "your_image_name")!)
As the function names imply, a UIColor is created from the image you supply.
Refer to the Apple documentation here for more information.
If you go into the Storyboard, you can insert the Image View, insert the image in, then in the Document Outline, you can move the image above the table and it'll go behind it. Then set the tableView alpha in the inspector as .5 or however opaque you want it.
Funny, I just did a tutorial that had this in it, but code was used instead of an image. In Swift 2.0 you create a file (let's call it BackgroundView) of type UIView, add this code in the pre-made drawRect function:
// Change these colors to the ones you want.
let lightPurple: UIColor = UIColor(red: 0.377, green: 0.075, blue: 0.778, alpha: 1.000)
let darkPurple: UIColor = UIColor(red: 0.060, green: 0.036, blue: 0.202, alpha: 1.000)
let context = UIGraphicsGetCurrentContext()
//// Gradient Declarations
let purpleGradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [lightPurple.CGColor, darkPurple.CGColor], [0, 1])
//// Background Drawing
let backgroundPath = UIBezierPath(rect: CGRectMake(0, 0, self.frame.width, self.frame.height))
CGContextSaveGState(context)
backgroundPath.addClip()
CGContextDrawLinearGradient(context, purpleGradient,
CGPointMake(160, 0),
CGPointMake(160, 568),
[.DrawsBeforeStartLocation, .DrawsAfterEndLocation])
Then create a configureView function in the TableViewController and put this line of code in it:
tableView.backgroundView = BackgroundView()
Then call the function in the viewDidLoad.

Change the Top Status Bar's Color of the SearchResultController (using Swift)

I created a Search Bar. When i search something, the SearchResultController appears and presents the result. But the status bar is all white ! The problem is that I changed the Status Bar Style to white and that the Status background is also white => EVERYTHING is white now :(
SCREENSHOT OF THE SITUATION HERE : http://omkia.com/screenshot1.png
How do you change the Status Background in this lines please ?
override func viewDidLoad() {
super.viewDidLoad()
UIApplication.sharedApplication().statusBarStyle = .LightContent
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
PS : I am using Xcode 6.3.2 and Swift.
Cheers !
Shouldn't you be changing the navigationbar color? If I'm understanding your problem.
let navbar = UINavigationBar.appearance()
navbar.barTintColor = UIColor(red: 22.0/255, green: 33.0/255, blue: 37.0/255, alpha: 1)
Try that and see if something changes.
You can't change directly to another color than white or black.
I've read that you can put a view behind the navigation bar and status bar, and change its color to the one of your interest.

Container View animations not working properly when changing out a background image

I have 3 Views in IB. View One(1) has a view and an UIImage that I am using as the background for this ViewController. View Two(2)(Details View) and View Three(3)(Options View) are Container Views, both placed on top of each other. I have a button in View 2 that animations View 2 off to the left and moves View 3 on from the right. And I am able to go back from View 3 moving off to the right and bringing View 2 back on from the left.
Here is what my Document Outline looks like:
The White BG, is just a background under the title bar, just in case anyone is wondering.
Here is the code that I am using to do those animations:
func showOptions(){
println("show options")
optionsView.center.x = self.view.bounds.width + self.view.bounds.width / 2
optionsView.hidden = false
UIView.animateWithDuration(0.5, animations: {
self.blurView.alpha = 1
// slide containers around
// println(self.view.bounds.width)
self.detailsView.center.x = -self.view.bounds.width * 2
self.optionsView.center.x = self.view.bounds.width / 2
})
}
func showDetails(){
println("show detials")
optionsView.center.x = self.view.bounds.width / 2
UIView.animateWithDuration(0.5, animations: {
self.blurView.alpha = 0
// slide containers around
// println(self.view.bounds.width)
self.detailsView.center.x = self.view.bounds.width / 2
println("\(self.optionsView.center.x) this is center X value 1")
self.optionsView.center.x = self.view.bounds.width * 2
println("\(self.optionsView.center.x) this is center X value 2")
}, completion: {
(value: Bool) in
println("\(self.optionsView.center.x) this is center X value 3")
})
}
Those that are curious as to what I get back from the print statements
When I don't try to change out the background photo and things work like this should I get this:
160.0 this is center X value 1
640.0 this is center X value 2
640.0 this is center X value 3
When I change out the photo I get these values:
160.0 this is center X value 1
640.0 this is center X value 2
160.0 this is center X value 3
What doesn't work is when I try to change out the image of the background view for this view controller.
let par3 = UIImage(named: "par3.png")
backgroundImage.image = par3
// println("par three")
showDetails()
When I change out the background photo, both View 2 and View 3 animate onto the screen from the left. I'm just not sure why. Does it have something to do with the width of the View Controller being modified when I switch out the background photo?
Thanks for the help!
It feels like you are only half embracing Auto Layout. You're using it to lay out your views initially, but then you're not using it when you're animating the views. You're modifying the center x position, which is the old way of animating. It's kind of working, but then I think everything is getting reset to your initial constraints when you swap out the background image, since that causes a call to layoutIfNeeded() under the hood, which uses the constraints to position everything back how it was initially.
Instead of modifying the center x position of the views you need to change your approach to modify the constraints instead. This will mean that even when you swap out the image your constraints will still be accurate and everything should work as expected. This requires setting up your constraints in a particular way, which I'll try and demonstrate.
First I have set up a parent view controller with a couple of child view controllers. Each of the children have a button that calls a delegate method back in the parent.
My storyboard looks like this:
Here's what the inspector looks like:
A few points about the storyboard setup:
I have a main container view that is a subview of the parent view. This main container view has clipSubviews set to YES, so that when we move views to the left and right they don't show up outside the bounds of the main container.
Both the options and detail container views have constraints set to match the width and height of the main container view.
The left side of the detail container view is set to butt up right against the right side of the options container view, i.e. [options][detail]. They are not directly on top of each other.
The detail container view has a constraint so that it's top edge lines up with the top edge of the options container view.
The options container view is set up with constraints to line up with the top left of the main container view. This combined with the previous 2 bullets means that initially the detail container view is off the screen to the right. I have a reference in the parent view controller to the constraint that lines up the left side of the options container view with the left side of the mainContainerView (the one that is selected in the above screenshot), and that's what I use to animate everything.
This is what the parent class looks like:
class ParentViewController: UIViewController, OptionsViewControllerDelegate, DetailViewControllerDelegate {
struct Constants {
static let optionsEmbedSegue = "optionsEmbedSegue"
static let detailEmbedSegue = "detailEmbedSegue"
}
#IBOutlet weak var mainContainerView: UIView!
#IBOutlet weak var optionsLeadingSpaceConstraint: NSLayoutConstraint!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.optionsEmbedSegue {
let childViewController = segue.destinationViewController as OptionsViewController
childViewController.delegate = self
} else if segue.identifier == Constants.detailEmbedSegue {
let childViewController = segue.destinationViewController as DetailViewController
childViewController.delegate = self
}
}
func optionsViewControllerDidSelectToShowDetail(optionsViewController: OptionsViewController) {
animateOptionsToNewXPosition(-CGRectGetWidth(mainContainerView.bounds))
}
func detailViewControllerDidSelectToGoBack(detailViewController: DetailViewController) {
animateOptionsToNewXPosition(0)
}
func animateOptionsToNewXPosition(xPosition: CGFloat) {
optionsLeadingSpaceConstraint.constant = xPosition
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.mainContainerView.layoutIfNeeded()
})
}
}
If you take a look at the optionsViewControllerDidSelectToShowDetail method, you'll see the line animateOptionsToNewXPosition(-CGRectGetWidth(mainContainerView.bounds)). This is moving the options view controller off to the left in the amount of the width of the main container view. This means that it will disappear off to the left of the screen, and because of all the other constraints, it will drag the detail view controller with it, revealing the detail view controller in the mainContainerView. The opposite happens in the detailViewControllerDidSelectToGoBack method, simply setting the constant in that constraint back to 0, which brings the options view controller back, and pushes the detailViewController off to the right.
I hope that helps.

Can NSCollectionView autoresize the width of its subviews to display one column

I have an NSCollectionView that contains a collection of CustomViews. Initially it tiled the subviews into columns and rows like a grid. I then set the Columns property in IB to 1, so now it just displays them one after another in rows. However, even though my CustomView is 400px wide, it's set to autoresize, the NSCollectionView is 400px wide, and it's set to 1 column, the subviews are drawn about 80px wide.
I know I can get around this by calling:
CGFloat width = collectionView.bounds.size.width;
NSSize size = NSMakeSize(width, 85);
[collectionView setMinItemSize:size];
[collectionView setMaxItemSize:size];
But putting this code in the awakeFromNib method of my WindowController only sets the correct width when the program launches. When I resize the window (and the NSCollectionView autoresizes as I've specified), the CustomViews stay at their initially set width.
I'm happy to take care of resizing the subviews myself if need be, but I'm quite new to Cocoa and can't seem to find any articles explaining how to do such a thing. Can someone point me in the right direction?
Anthony
The true answer is to set the maxItemSize to 0,0(NSZeroSize). Otherwise, it is computed.
[self.collectionView setMaxItemSize:NSZeroSize];
This can be set in awakeFromNib.
I couldn't get this to work with a default layout - but it is fairly easy to implement a custom layout:
/// Simple single column layout, assuming only one section
class SingleColumnLayout: NSCollectionViewLayout {
/// Height of each view in the collection
var height:CGFloat = 100
/// Padding is wrapped round each item, with double an extra bottom padding above the top item, and an extra top padding beneath the bottom
var padding = EdgeInsets.init(top: 5, left: 10, bottom: 5, right: 10)
var itemCount:Int {
guard let collectionView = collectionView else {
return 0
}
return collectionView.numberOfItems(inSection:0)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
override open func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? {
let attributes = NSCollectionViewLayoutAttributes(forItemWith: indexPath)
guard let collectionView = collectionView else {
return attributes
}
let bounds = collectionView.bounds
let itemHeightWithPadding = height + padding.top + padding.bottom
let row = indexPath.item
attributes.frame = NSRect(x: padding.left, y: itemHeightWithPadding * CGFloat(row) + padding.top + padding.bottom , width: bounds.width - padding.left - padding.right , height: height)
attributes.zIndex = row
return attributes
}
//If you have lots of items, then you should probably do a 'proper' implementation here
override open func layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes] {
var attributes = [NSCollectionViewLayoutAttributes]()
if (itemCount>0){
for index in 0...(itemCount-1) {
if let attribute = layoutAttributesForItem(at: NSIndexPath(forItem: index, inSection: 0) as IndexPath) {
attributes.append(attribute)
}
}
}
return attributes
}
override open var collectionViewContentSize: NSSize {
guard let collectionView = collectionView else {
return NSSize.zero
}
let itemHeightWithPadding = height + padding.top + padding.bottom
return NSSize.init(width: collectionView.bounds.width, height: CGFloat(itemCount) * itemHeightWithPadding + padding.top + padding.bottom )
}
}
then all you need is
var layout=SingleColumnLayout()
collectionView.collectionViewLayout = layout
I know this is a very late response but I got the same problem and hope my solution will help somebody. Solution is to access bounds of enclosing scroll view not of collection view itself. So to solve it you need to replace first line with:
CGFloat width = collectionView.enclosingScrollView.bounds.size.width;
another late one - I just switched to using an NSTableView and providing an NSView by the delegate method.
Autoresizing comes for free, one column is easy, and it renders massively faster.
Lets say you want your CollectionViewItem with a size of 200x180px, then you should set:
[myCollectionView setMinItemSize:NSMakeSize(200, 180)];
[myCollectionView setMaxItemSize:NSMakeSize(280, 250)];
Your Max-Size should be big enough to look good and give enough space for stretching to fit the collectionView-Width.
If you have a fixed number of columns, you can probably use (0,0), but if you want a dynamic number of rows and columns like I wanted.. you should set a fixed min-size and a bigger max.size.
While you might get a collection view to behave as you want, I think you have a design problem
You should use a NSTableView and set columns to 1 and their sizing to anything but "None". NSTableView is intended for tabular data, plus it can recycle cells which gives a great performance boost for large amount of items. NSCollectionView is more like a linear layout which arranges items in a grid, with vertical scrolling. It is useful when the column number can change dynamically to allow more items to be shown on screen, usually depending on device orientation and screen size.
I tried all of solutions proposed here and none of them helped. If you use flow layout (it's used by default) you can extend it with the following code and delegate's sizeForItem method will be called on each change
class MyCollectionViewFlowLayout: NSCollectionViewFlowLayout {
override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
override func invalidationContext(forBoundsChange newBounds: NSRect) -> NSCollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! NSCollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = true
return context
}
}
Hope it helps someone as it took me couple of evenings to find solution
Matt Gallagher's wonderful blog Cocoa with Love is about to address this. This week, he shared the bindings details of a one-column view like the one in question:
http://cocoawithlove.com/2010/03/designing-view-with-bindings.html
Next entry, he promises to share the rest of the implementation, which should give you what you're looking for.
(I should note that he is subclassing NSView directly and re-implementing many of NSCollectionView's features. This seems to be common though; not many people are fond of subclassing NSCollectionView.)
Edit: Seems he broke his promise... we never did receive that post. See tofutim's answer below instead.

Resources