First off, I really want to thank the guys who have built snapkit. It has really made setting up constraints for UIViews really easy.
But for now, I have a simple question: How can I access the frame property of a view I setup using this library?
For example:
self.view.addSubview(contributePosterView)
self.contributePosterView.snp.makeConstraints { (make) in
make.left.equalTo(self.view.snp.left)
make.width.equalTo(self.view.bounds.width)
make.top.equalTo(self.table.snp.bottom)
make.bottom.equalTo(self.view.snp.bottom)
}
How can I access the frame property of the view which I have named as contributePosterView?
This is important to me especially when I have to set them up in a UIScrollview using layoutSubviews property of the said scroll view.
I checked the snapkit documentation as much as I could but still have not found an answer.
How should I go about this? Any help would be appreciated.
"SnapKit" provides methods to add constraints, using a syntax that many people find easier than the default NSLayoutContraint methods. However, it doesn't do anything to the views to make it impossible to get the resulting frames sizes.
The issue is that you are likely making your "snap" calls in viewDidLoad(), and then immediately trying to get the frame. At that point, all that has happened is that the constraints have been added, but auto-layout has not done its work.
You want to override viewDidLayoutSubviews(), at which point you can get the valid frame size:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// now you can get the resulting frame
let f = self.contributePosterView.frame
// do what you want with the frame
}
In the screen shots below, the only change I have made is to connect an IBOutlet for a constraint. Unconnected, screen lays out correctly. Connected, the screen lays out incorrectly. I have not seen this happen before and don't know what to try to fix it. I need to be able to modify the constant value of the constraint in order to resize a subview depending on the presence or absence of a particular item.
The IBOutlet is declared as:
#IBOutlet weak var tabContentBottomConstraint: NSLayoutConstraint!
So far, I have not implemented any code that reads or modifies this constraint. I added it to my view controller in preparation for using it, but have not gone any further because when I run the code after adding the IBOutlet, the view sizes incorrectly. I have added and removed several times, always with the same result.
Here is the debugger info on the view sizings prior to connecting the outlet:
And here is the debugger info after connecting the IBOutlet with no other code change whatsoever:
The difference in y offset is huge and pushes the view off the screen. As I said above, no code reads or writes to that IBOutlet.
The rolePageDrawerView is item2 in the constraint, and item1 is the view controller's view.safeAreaLayoutGuide. rolePageDrawerView is embedded 3 levels deep into child views of view.safeAreaLayoutGuide.
I found a way to work around this weird behavior by programmatically searching out the constraint with the following code:
guard let tabContentBottomConstraint = view.constraints.filter({ $0.secondItem! as! NSObject == rolePageTabContentView }).first else {
fatalError("unable to find the constraint for drawer sizing")
}
self.tabContentBottomConstraint = tabContentBottomConstraint
I left the constraint as a weak reference even though it is no longer injected since the view itself will have a reference to it to keep it alive. It works.
HOWEVER, I'm leaving the question open for a better solution. The above search is very fragile some the layout change in the future. It would be much better if the framework was injecting the constraint without breaking the layout. Still open for better solutions.
In my xib file, I have a constraint for height for the label in my xib file.
And in my objective c, I tried to change it using
self.heightConstraint.constant = newHeight;
But nothing is changed when I run it in simulator. I have used debugger and make sure that line is executed. And in Spark tool, I see the label height is the old height.
How can I adjust the height dynamically?
Updated:
I have changed my code to add a nil check.
if (self.heightConstraint != nil) {
self.heightConstraint.constant = newHeight;
}
My code still get executed, but nothing get changed.
There's nothing wrong with your code itself:
self.heightConstraint.constant = newHeight;
But the reason this changes nothing in your interface, even though it is being executed, is that self.heightConstraint is not a reference to a constraint in your interface. (It is probably nil.)
I agree with Matt. The likely reason your code isn't working is that you have a broken outlet link. In general, when code that tries to do something with an outlet or action doesn't work, the most likely cause is a broken outlet or action connection.
Rule of thumb: When debugging stereo or computer hardware, check the cables.
Corollary for for iOS or Mac OS apps in Xcode: When debugging UI code, check your IBOutlet and IBAction connections.
At what part of the view controller lifecycle are you changing the constraints? You may need to call setNeedsLayout on the parent view.
I have the following set of code:
CustomView.h
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface CustomView : UIView
#property (nonatomic) IBInspectable UIColor *borderColor;
#property (nonatomic) IBInspectable CGFloat borderWidth;
#property (nonatomic) IBInspectable CGFloat cornerRadius;
#end
CustomView.m
#import "CustomView.h"
#implementation CustomView
- (void)setBorderColor:(UIColor *)borderColor {
_borderColor = borderColor;
self.layer.borderColor = borderColor.CGColor;
}
- (void)setBorderWidth:(CGFloat)borderWidth {
_borderWidth = borderWidth;
self.layer.borderWidth = borderWidth;
}
- (void)setCornerRadius:(CGFloat)cornerRadius {
_cornerRadius = cornerRadius;
self.layer.cornerRadius = cornerRadius;
}
#end
(For Swift reference, this problem was also occurring with Swift code)
CustomView.swift
#IBDesignable
class CustomView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#IBInspectable var borderColor : UIColor = UIColor.clearColor() {
didSet {
self.layer.borderColor = borderColor.CGColor
}
}
#IBInspectable var borderWidth : CGFloat = 0.0 {
didSet {
self.layer.borderWidth = borderWidth
}
}
#IBInspectable var cornerRadius : CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
}
I added a UIView to a view controller on the storyboard and set its subclass to CustomView.
This adds the "Designables" row. It is stuck on "Updating" and the tooltip says "Waiting for Target to Build". It never changes from this status.
When I move to the attributes inspect, I am able to set these IBInspectable properties:
And once set, they also show up in the "User Defined Runtime Attributes":
However, the "Designables" status never moves beyond "Updating" with still the same tooltip (I've tried Cmd+B building several times, nothing changes).
Moreover, as I set the IBInspectable properties, I get a warning for each one:
IBDesignables - Ignoring user defined runtime attribute for key path "borderColor" on instance of "UIView" ... this class is not key-value coding-compliant for the key borderColor.
Screenshot of the warnings generated:
I am familiar with the key-value coding-compliant issues and generally know how to solve them... but I don't understand how to solve this issue here. According to the view's identity inspector, the view is a "CustomView" (not a regular "UIView", which doesn't have these properties). And if the view weren't a "CustomView" then these designable properties wouldn't show up in the Attributes Inspector, right? But when Interface Builder tries to apply these attributes to the view, it goes back to thinking the view's class is "UIView" and cannot apply the attributes.
Any help? Please let me know if I've left out some important detail, but for what it's worth, I followed this tutorial exactly (other than ObjC vs Swift). It's also worth noting that I followed this tutorial exactly on another machine and it worked like a charm (I intended to make this post last night but the computer I was on then didn't have this issue).
Based on comments, it has been suggest that perhaps the .m file isn't included and that might be causing the problem. I thought surely I would have gone out of my way for this scenario to be the case, but I checked anyway.
When I first started attempting to do this, I was under the understanding that the IB_DESIGNABLE classes had to be part of a different UIKit framework. So from this first screenshot, you can see that I set up a "CustomViews" framework, which has one class, CustomView. You'll also see here that I also created a OtherView, which is identical to CustomView, except it's not in a separate framework. The identical problem persists on the storyboard between both classes however.
Here we have a screenshot indicating that CustomView.m is included to be built with the CustomViews framework:
Meanwhile, the following screenshot indicates several things:
CustomViews.framework is appropriately included in the main project.
OtherView.m is also included as a compile source, so even if something is wrong with CustomView, OtherView should work, however it generates identical errors.
Main.storyboard and LaunchScreen.xib are showing up as red. I have no idea why, and haven't the slightest clue as to why LaunchScreen.xib should (I haven't touched this file), though I can say after looking at other projects, Main.storyboard also shows up in red for those projects, and I'm not doing anything with IB_DESIGNABLE or IBInspectable there.
I have tried and retried this several times now. It works every time on my computer at home--I can not reproduce the problem described in this question at home. At work, it never works. The problem described in this question happens every time.
Both computers are Mac Minis purchased new this year (not the new models, late 2012 model). Both computers are running OS X Yosemite 10.10. Both computers are running Xcode Version 6.1. At home, the build is (6A1052d). This morning, I can confirm that both computers are running identical builds of Xcode.
Others have suggested to me that it might be bad RAM. That seems far fetched to me. I've restarted the project multiple times, restarted the computer multiple times. Seems to me if there were bad RAM on a computer approximately 6 months old, that I'd be seeing other problems, and that this problem would be less consistent. But this exact problem persists despite numerous times restarting the entire project from scratch and full restarts on the computer.
It should be worth noting that if I actually compile and run this project, the custom view with the IBInspectable properties actually displays as I expect the storyboard to display it. I imagine that this would be the case even with out the IB_DESIGNABLE and IBInspectable directives however, as these are created as User Defined Runtime Attributes.
Based on chrisco's suggestion to debug the selected view (which I had already done, but went to try again for good measure), I noticed a couple of other options at the bottom of the Editor menu.
Automatically Refresh Views
Refresh All Views
I clicked "Refresh All Views" and after Xcode had a bit of a think, suddenly the storyboard was displaying my view as expected (properly applying my IBInspectable properties).
I then went through the whole process again to confirm that this is the solution.
I created a new class, ThirdView. This class is identical to the others, again. I changed my view's class to ThirdView and got something slightly different this time:
Clicking "Show" to me to the warnings:
A new one this time:
Using class UIView for object with custom class because the class ThirdView does not exist.
This isn't really any more helpful than what already existed. Plus, now the other three warnings have doubled into 6 strangely.
Anyway, if I click "Refresh All Views" from the Editor drop down menu again, all the errors go away, and once again, the view properly displays.
Still, up to this point, everything I did was stuff I never messed with at home. At home, it just worked. So I turned on "Automatically Refresh Views" and created a "FourthView" to test--once again, identical to the first three.
After changing the view's class to "FourthView" the designables label said "Updating" for a short moment then finally said "Up to date":
So, I checked my computer at home. "Automatically Refresh Views" is turned on at the computer that was always working. It was turned off at the computer that wasn't. I don't ever remember touching this menu option. I can't even tell you for sure whether it existed before Xcode 6. But this option is what was making the difference.
TL;DR, if you're having the same problem described in the question, make sure "Automatically Refresh Views" is turned on (or manually "Refresh All Views" when you need an update in IB):
Just a quick hint for anyone else having this problem: remember to specify the type of the variable.
// Doesn't show up in IB
#IBInspectable var includeLeftSection = true
// Shows now that it knows the type
#IBInspectable var includeLeftSection : Bool = true
I have a few more details that may cause your IBDesignable classes to not be loaded.
Select your problematic storyboard/xib where your custom views ought to display.
In the navigator area, head to the Report Navigator in your XCode workspace/project.
In the Editor menu of XCode, hit (as mentioned by nhgrif), the "Refresh All Views" option. This will cause IB to launch a compile for a whole bunch of stuff that you, I'm certain, would not expect.
In the Report Navigator, Click on "By Group" to filter content and look at the "Interface Builder" section. You will see that for the sake of loading the custom IBDesignable views framework, it will compile LOTS of things. If any of these targets do NOT compile, such as (perhaps deprecated) unit test targets (even if they are totally unrelated to the code that loads these views or storyboard), then IB will fail at loading your dll.
In my case, IB tried to compile 8 targets, including 4 that where unit tests that had not been updated since recent refactoring changes we've been working on.
Most of the code changes/fixes I have done in order for IB to properly load and display my customs views where not related or even linked against these classes, nor would it ever load the storyboard in the course of running these unit tests. Yet, IB had a dependency on the whole workspace compiling for it to work.
I had the same warning Ignoring user defined runtime attribute for key path .. even though I am absolutely sure I didn't do anything wrong with my custom IBDesignable view class.
Turned out, in my case, it got to do with Xcode cache.
rm -rf ~/Library/Developer/Xcode/DerivedData/*
Purge DerivedData and the warning is gone.
Incase any one else comes up against the error IB Designables class does not exist, for the same reason as I did. Top answer was not my issue... but here is a slightly related problem...
There is a property hidden in the story board source code called customModule.
For example I had a class called ForwardArrow inside a separate framework that I accidentally added to my main target.
So the XML for some views ended up as
customClass="ForwardArrow" customModule="MainTargetNameWasHere"
When I removed them from the main target in the build the story board did not update MainTargetNameWasHere to CustomViews which is the framework where it was located and started giving that no class found error.
So TLDR; Make sure that if your IBDesignable is in another framework that the customModule xml attribute in your story board is set to the right value. And if it isn't there at all add it.
Example from my source:
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="MUG-jc-2Ml" customClass="ForwardArrow" customModule="CustomViews">
As my example, I was using CheckboxButton via pod and the graphics of checkbox never shows up in the storyboard while I got the same issues described in the question here:
warning: IB Designables: Using class UIView for object with custom class because the class CheckboxButton does not exist
and
warning: IB Designables: Ignoring user defined runtime attribute for key path "checkColor" on instance of "UIView". Hit an exception when attempting to set its value: [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key checkColor.
The way solved my problem was to supply the module with name CheckboxButton as below:
Note: you should replace CheckboxButton to whatever the name of module you are using.
I personally solved this problem by using the "-" button to delete content from my identity inspector. When you remove custom classes, change content in the IB and then add a new custom class, the designable elements in the identity inspector don't get removed and it caused me to have that error. Just Delete everything and rebuild.
I know this is answered, but here is one more experience.
I was having some problems unrelated to this issue, but in the process I removed #IBInspectable from the vars in my class and deleted the attributes from the identity inspector (alt-apple-3).
After fixing the (code) issue with the component, I refreshed everything a ton of times, but still no attributes in the identity inspector.
Eventually, I noticed that they were back, but only in the attributes inspector (alt-apple-4). As soon as I added values to them there, they re-appeared in the identity inspector
Dave Thomas's answer above gave me the (reverse) solution when not of the others (Derived Data, Editor > Refresh) did, but for the sake of clarity in case people aren't sure where to edit the XML... you don't need to!
In your storyboard file select the troublesome view
On the right-hand sidebar select the Identity Inspector tab (3rd option from the left).
You'll have your custom class, which should already be set, and the Module. For me this was empty, and I was getting the same errors as OP. I set the Module to my project name and BAM - it started working after rebuilding!
I just went through the ringer on this problem. I tried all the things listed here and elsewhere without any luck. This is a storyboard that worked fine forever and it suddenly stopped working with the "Ignoring user-defined runtime attribute..." problem.
For whatever reason, removing this code from one of my IBDesignable's fixed it:
-(void)viewDidLoad {
self.clipsToBounds = YES;
}
removing this caused all the warnings to go away, even in other IBDesignable objects. I have no idea why this one step fixed it, but maybe it will help someone else too.
I was having the same problem and I had to change the cornerRadius and BorderWidth to be a String and then cast it to CGFloat, it was the only solution for me to be able to change the values and see the changes in interface builder.
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor!.CGColor
}
}
#IBInspectable var borderWidth: String? {
didSet {
layer.borderWidth = CGFloat(Int(borderWidth!) ?? 0)
}
}
#IBInspectable var cornerRadius: String? {
didSet {
layer.cornerRadius = CGFloat(Int(cornerRadius!) ?? 0)
layer.masksToBounds = layer.cornerRadius > 0
}
}
I have a fairly basic MainWindow.xib with a source list-style sidebar. I created it by dragging the Source List template into the window, which already contains two NSTableCellViews: HeaderCell and DataCell.
The latter consists of an icon (using NSImageView) and a label (NSTextField). Instead, I want the label and another, smaller label underneath. In IB, this looks as follows:
If I focus on just DataCell, it highlights accordingly:
Thing is, actually running the program, it looks nothing like the template:
Notice how the two NSTextFields just get smashed together into one. My understanding was that view-based NSOutlineViews (and view-based NSTableViews, for that matter) are supposed to be designed as a template from within IB. Instead, the dimensions from the template seem to get mostly ignored.
Here's the code that sets the view's values from the data source:
public class TourSourceListDelegate : NSOutlineViewDelegate
{
public override bool IsGroupItem(NSOutlineView outlineView, MonoMac.Foundation.NSObject item)
{
return (item as TourSourceListDataSource.Item).IsHeader;
}
public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn, MonoMac.Foundation.NSObject item)
{
if (IsGroupItem(outlineView, item))
{
return outlineView.MakeView("HeaderCell", this);
}
else
{
var data = item as TourSourceListDataSource.Item;
var dataView = outlineView.MakeView("DataCell", this);
(dataView.Subviews[0] as NSTextField).StringValue = data.Name;
(dataView.Subviews[1] as NSTextField).StringValue = data.Date_start.ToShortDateString();
return dataView;
}
}
}
I've tried overriding GetRowHeight, but that doesn't seem to resolve the problem (it makes more room, but still doesn't let the views distribute themselves properly), nor does it seem necessary.
I've also tried playing with the various Autosizing, Autoresizes Subviews, etc. toggles in IB, but that doesn't seem to produce intuitive results, and again, it doesn't seem necessary — the view as presented in IB is exactly what I want, just with slightly longer labels in practice.
I haven't tried converting this to AutoLayout yet.
What obvious step am I missing?
Some more info that probably doesn't make a difference: this is a Xamarin.Mac/MonoMac project with Xcode 5.0, MacOSX10.8.sdk, Xamarin Studio 4.0.12, Xamarin.Mac 4.0.12, and Mono 3.2.3 (targeting Mono / .NET 4.0). I've also enabled App Sandboxing.
What's important in interface builder is the view hierarchy. What kind of view is that cell? Are those labels really subviews of the cellview or not? The hierarchy should look something like:
One thing that's fishy that I see is accessing dataView.Subviews[0] and [1]. If you're adding subviews to your cells then should be creating your own NSTableViewCell subclasses, with each view connecting to the subclass' IBOutlet properties. The subclass doesn't need any code in its implementation, just the declaration of its properties in #interface, such as titleField and descriptionField, and an empty #implementation that auto-synthesizes them.
Then makeViewWithIdentifier (or apprently the glue MakeView in Xamarin) when passed the right identifier should create your NSTableViewCell subclass, and at runtime you can verify that using po dataView in the debugger. Then you access the subviews using the properties of your NSTableViewCell subclass' interface instead of assuming which view is in which position with the subview array, using dataView.titleField and dataView.descriptionField.
If your cell view has one text field then you can use NSTableViewCell without subclassing, but do connect up the textField outlet (its connected by default as long as you don't delete & recreate the cell view's label view) so you can access it through the property, again instead of having to dive into the subviews array.
All that said, it's not really clear why you're seeing what you are. It looks like those aren't the subviews you expect, and might even look like the wrong fonts as well as in the wrong positions. Using a custom subclass of NSTableViewCell and verifying its class at runtime is a good way of making sure it's creating the view you expect, but you can also dump the subview within the debugger using po [dataView _subtreeDescription].