This is a feature a like a lot when defining layouts for Android and that allows to define attributes just to design.
http://tools.android.com/tips/layout-designtime-attributes
But I didn't find any equivalent way to do the same when using storyboard to make iOS Apps.
At the moment I a cleaning all the design values on a viewDidLoad of my ViewController. Is there a way to define layout attributes as design placeholders and avoid making this setupClean step in all my view controllers?
When You want Instant Reflect of attributes/properties of particular componet in storyboard then you can achieve using #IBDesignable.
To achieve this You have to subcalss of that type of which you want to be reflected on storyboard.
Steps
Make a subclass of the type you want be reflected on storyboard, as in example here i am going to subclass UILabel as DGlabel
Make that calss as #IBDesignable, see the example
Assign that subclassed to the component from Identity Inspector, see screen shot
In subclassed calss (here in example DGLabel) declare variables of properties/attributes which you want be reflected on design as #IBInspectable, see example here i have decalred borderColor as #IBInspectable, this means this property will be listed on property inspector of Xcode
now you can change the value of that property from story board as screen shot below
import UIKit
#IBDesignable
class DGLabel: UILabel {
#IBInspectable var borderColor:UIColor = UIColor.red {
didSet {
reflectChange()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func reflectChange() {
self.layer.borderWidth = 1
self.layer.borderColor = borderColor.cgColor
}
}
Hope this will allow you to understand the process.
Yes you can do that by providing runtime attribute in "User defined runtime attributes" section under storyboard's Identity inspector. See below screenshot for setting UILabel's "text" keypath's runtime value as blank.
I think you can achieve something similar by using IBDesignable for your views. Then you may use prepareForInterfaceBuilder() and TARGET_INTERFACE_BUILDER to generate mock data for display in Interface Builder.
Did you mean this?
You can change Label Text value in storyboard from the "Attribute Inspector".
Related
Very much like how we can set the Global Tint in Storyboard, Is it possible to set the global font-family as something else?
For example, I want to change the Global/Default Font from System to Source Sans Pro 16pt. However, what I have to do (to my knowledge) is one of the following:
Change font of each label, button, textField, etc. in Storyboard.
Set it via Swift ViewDidLoad Code (like this question) or through extensions as explained in this question
My Problem with (2) is that I do not get Visual Feedbacks like in (1) using storyboards. On the other hand, it is also not very eloquent as I have to manually set it anyway.
So, is there a way to change/set the default Storyboard font?
You can use the Appearance API, to ensure that all controls have the same font (at runtime)
Look at this question as to how to set the font for UIButton with Appearance.
For UILabel and UITextView, do the following. This can be done in AppDelegate application(_:didFinishLaunchingWithOptions:)
let labelAppearance = UILabel.appearance()
labelAppearance.font = UIFont.myFont()
let textFieldAppearance = UITextView.appearance()
textFieldAppearance.font = UIFont.myFont()
The previous solution, however will not update storyboard.
To see the changes visually on storyboard, you can look into the function
prepareForInterfaceBuilder()
Here is an answer that explains how to get it visually updated in storyboard, but for this you will need to use custom classes for your textFields, buttons, etc.
Code Example as per above link:
#IBDesignable
public class MyUILabel: UILabel {
public override func awakeFromNib() {
super.awakeFromNib()
configureLabel()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
configureLabel()
}
func configureLabel() {
font = UIFont(name: Constants.DefaultFont, size: 40)
}
}
I have a
#IBDesignable
class Fancy:UIButton
I want to
addTarget(self, action:#selector(blah),
forControlEvents: UIControlEvents.TouchUpInside)
So where in UIButton should that be done?
Where is the best place for addTarget ?
1 - I have seen layoutSubviews suggested - is that right?
Note - experimentation shows that a problem with layoutSubviews is that, of course, it can be called often, whenever things move around. It would be a bad idea to "addTarget" more than once.
2 - didMoveToSuperview is another suggestion.
3 - Somewhere in (one of) the Inits?
Note - experimentation shows a fascinating problem if you do it inside Init. During Init, IBInspectable variables are not yet actually set! (So for example, I was branching depending on the "style" of control set by an IBInspectable; it plain doesn't work as #IBInspectable: won't work when running!)
4 - Somewhere else???
I tried to do it in Init, and it worked well. But it breaks designables from working in the Editor.
By thrashing around, I came up with this (for some reason both must be included?)
#IBDesignable
class DotButton:UIButton
{
#IBInspectable var mainColor ... etc.
required init?(coder decoder: NSCoder)
{
super.init(coder: decoder)
addTarget(self, action:#selector(blah),
forControlEvents: UIControlEvents.TouchUpInside)
}
override init(frame:CGRect)
{
super.init(frame:frame)
}
I don't know why that works, and I don't understand why there would be two different init routines.
What's the correct way to include addTarget in a UIButton?
tl;dr
override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
super.endTrackingWithTouch(touch, withEvent: event)
if let touchNotNil = touch {
if self.pointInside(touchNotNil.locationInView(self), withEvent: event) {
print("it works")
}
}
}
Why not use addTarget
addTarget method is part of action-target interface which is considered 'public'. Anything with reference to your button can, say, remove all of its actions, effectively breaking it. It is preffered to use some of 'protected' means, for instance endTrackingWithTouch which is accessible only to be overriden, not called directly. This way it will not interfere with any external objects using action-target mechanism.
(I know there is no strict 'public' or 'protected' in ObjC/UIKit, but the concept remains)
Your way
If you want to do it exactly your way then your example is all good, just copy addTarget call to init(frame:CGRect).
Or you can put addTarget in awakeFromNib (don't forget super) instead of init?(coder decoder: NSCoder), but you will be forced to implement init with coder anyway, so...
layoutSubviews and didMoveToSuperView both are terrible ideas. Both may happen more than once resulting in blah target-action added again. Then blah will be called multiple times for a single click.
By the way
The Apple way
By the Cocoa MVC (which is enforced by UIKit classes implmentation) you should assign that action to the object controlling that button, animations or not. Most often that object will be Cocoa MVC 'Controller' - UIViewController.
If you create button programmatically UIViewController should assign target to itself in overridden loadView or viewDidLoad. When button is loaded from nib the preffered way is to assign target action in xib itself.
Old Good MVC
As mentioned here in real MVC views do not send actions to themselves. The closest thing to real MVC Controller in UIKit is UIGestureRecognizer.
Be warned that it's pretty difficult to pull of real MVC with UIKit class set.
You should not add as target the same object that produces the action.
The target and its callback should be another object, usually a view controller.
There are 2 inits methods because the button can be instantiated by calling init or by the process of deserializion (NSCoder) from a nib/xib. Since you probably added the button to a storyboard the init method called is init?(_: NSCoder).
[UPDATE]
I agree about what you say in the comment, but I think that the action-target pattern should be used for communicating with other objects, I'm using conditional, because as far as I know I never seen something like what you wrote in Apple code or some other library. If you want to intercept and make some actions inside the button you should probably override some of the methods exposed in UIControl.
About designable, you are, again, correct. init(frame) is called if you are creating a button programmatically, init(coder) if the button comes from a xib.
The method init(frame) is also called during designable process. At this point I think that the best option is to debug directly your view.
Place some breakpoints inside you UIButton subclass
Select the view in your storyboard
Go to the Editor -> Debug selected views
Now you should be able to understand where the problem is.
How about implementing awakeFromNib and doing it there?
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSNibAwaking_Protocol/#//apple_ref/occ/instm/NSObject/awakeFromNib
You can also conditionally run (or not run) code when it is being run in the context of Interface Builder:
#if !TARGET_INTERFACE_BUILDER
// this code will run in the app itself
#else
// this code will execute only in IB
#endif
(see http://nshipster.com/ibinspectable-ibdesignable/)
Your initialize method is not correct, this will work:
```swift
override init(frame: CGRect) {
super.init(frame: frame)
self.loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.loadNib()
}
private func loadNib() {
let nibView = NSBundle(forClass: self.classForCoder).loadNibNamed("yourView", owner: self, options: nil).first as! UIView
nibView.frame = self.bounds
nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.button.addTarget(self, action: #selector(action), forControlEvents: .TouchUpInside)
self.addSubview(nibView)
}
```
I'm using a custom UICollectionViewCell class for my UICollectionView. I have to use addSubview in my custom class because I'm using FirebaseUI-iOS. This is what my MessageCollectionViewCell looks like:
import Foundation
import UIKit
class MessageCollectionViewCell: UICollectionViewCell {
#IBOutlet var messageContainerView: UIView?
#IBOutlet var messageText: UILabel?
#IBOutlet var messageDisplayName: UILabel?
#IBOutlet var messageUserImage: UIImageView?
#IBOutlet var messageUserImageOverlay: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
// Custom initialization code for label
let size = self.contentView.frame.size
let frame = CGRectMake(0.0, 0.0, size.width, size.height)
self.messageContainerView = UIView(frame: frame)
self.messageUserImageOverlay = UIView(frame: frame)
self.messageText = UILabel(frame: frame)
self.messageDisplayName = UILabel(frame: frame)
self.messageContainerView!.addSubview(self.messageUserImageOverlay!)
self.messageContainerView!.addSubview(self.messageText!)
self.messageContainerView!.addSubview(self.messageDisplayName!)
self.contentView.addSubview(messageContainerView!)
}
required init(coder aDecoder: NSCoder) {
println("Init")
super.init(coder: aDecoder)
}
}
I have constraints in my Storyboard file which I want to use, but when I'm using addSubview my constraints does not get used. Is there anyway I can use addSubview() and still keep the constraints? I know that I can add it programmatically, but I wish to use the constraints that i've already set inside Storyboard.
This issue is due to how FirebaseUI 'FirebaseCollectionViewDataSource` registers it's classes. I don't believe it's possible to instantiate a class like this and get the autolayout properties from a XIB like you're asking, but it is possible to solve the problem a layer back by fixing our handling of prototype cells.
The problem here is that we're registering the cell reuseIdentifier twice: once in the storyboard and once in code (FirebaseCollectionViewDataSource must do this in order to dequeue cells). Since we call ours second, it overwrites the first one, which means that none of your outlets are populated, layouts are weird, etc. This means that you have to set them up as if you were using regular subclasses rather than XIBs. The quickest thing you could do here is just use a XIB instead of a prototype cell (which is just a XIB inside the storyboard). So, how can we support FirebaseUI + prototype cells...
The short answer is that currently this feature isn't possible due to Apple's design of UICollectionView.
Unlike UITableView, which can check for this behavior by dequeuing a cell (which would return an instantiated prototype cell and tell us that the reuseidentifier has already been created) like we do in FirebaseTableViewDataSource here, UICollectionView doesn't provide a similar method, it only gives:
func dequeueReusableCellWithReuseIdentifier(_ identifier: String,
forIndexPath indexPath: NSIndexPath!) -> AnyObject
Given that this method requires an a indexPath as it has to return a non-nil object, it will throw an NSInternalInconsistencyException when we try to read an arbitrary object at initialization (since there exist no items for it to read from). Additionally, there doesn't appear to be any way to programmatically check if a reuseIdentifier is in use. This leaves us with a few options:
Recommend people not use prototype cells and instead do custom subclasses or XIBs and hook them in. Storyboards and prototype cells feel a little more brittle (mostly for reasons like this), but definitely have ease of use going for them.
Pull the -registerClass: forReuseIdentifier: call out of FirebaseCollectionViewDataSource, though this means that FirebaseTableViewDataSource should be changed as well (even though it can work) and make the developer explicitly call this (or not in the case of using a Storyboard).
Add a parameter for storyboards to the initialization call which would still retain the reuseIdentifier to dequeue cells, but not register the class.
Try to dequeue a cell, catch the NSException, register the class and try again. This works, but it throws the exception still and adds a little more code in the runloop (we have to wrap the call with try-catch that we know will fail at most once).
My personal preference is 1, but the value proposition for prototype cells is high enough that 3 might be the best option for this library. I'd rather not do 2, since cell registration is a difficult enough problem.
For now, I'd recommend using XIBs instead of prototype cells, or waiting for us to pick one of the above solutions (we can push a release pretty quickly to solve the problem).
I'm using UIAppearance a lot for my navigation controllers and other UI objects, and I was wondering if it's possible to have my related objects styled in Interface Builder (maybe with some magic voodoo of #IBDesignable?)
matt's comments on the question are correct. You're able to successfully use UIAppearance proxies in the prepareForInterfaceBuilder method.
Example:
#IBDesignable
class MyCustomView: UIView {
override func prepareForInterfaceBuilder() {
MyCustomView.appearance().backgroundColor = UIColor.redColor()
}
}
This will result in all MyCustomView instances to be rendered red in IB.
I'm using storyboards for the first time in iOS 8 and so far have been loving the live rendering aspect of things on the storyboard. However, I seem to have hit a snag in getting my views to render properly on the storyboard.
I have a container UIView that contains a connection to a UILabel on the storyboard, I am attempting to set the label's text based on an IBInspectable attribute on the label's parent container view.
#IBDesignable class ContainerView : UIView {
#IBOutlet weak var : titleLabel : UILabel!
#IBInspectable var title : String = "" {
didSet {
titleLabel?.text = title
}
}
/* Init functions */
prepareForInterfaceBuilder() {
self.titleLabel?.text = title
}
}
If I set the attribute in the storyboard it renders as expected while the program is executing but fails to render in the storyboard as I would expect. I've checked my connections and everything appears to be hooked up properly.
My question is: Is it possible to affect the contents of an IBOutlet connected view via IBInspectable attributes and have them live render on the storyboard, and if so, what am I missing or doing wrong?
Unfortunately you can't see IBOutlet objects in interface builder for your custom views which are marked as IBDesignable. If you want to see your outlets in interface builder, you have to use regular variables instead IBOutlet and you have to create your objects programmatically.
Also please note that, if you need to change something from interface builder for your objects, you have to define your properties as IBInspectable. Currently following variables types are valid for IBInspectable:
Bool, CGFloat, CGPoint, CGRect, CGSize, NSInteger, NSString, UIColor, UIImage
I hope this answer is adequately clear for you.
Edit: I found following article which is describing a way how to do what you need:
http://justabeech.com/2014/07/27/xcode-6-live-rendering-from-nib/
2nd Edit: I tried the article and it works. Now I can see my outlets on interface builder