Accessing "text styles" in Interface Builder and/or Storyboards - ios

In one of my apps I have a styles document with methods for different text styles, for example:
+(UIFont*)h1{
return [UIFont fontWithName:#"Helvetica" size:48.0];
}
Then, in the viewDidLoad methods of each my view controllers, I set the text styles programmatically. It's been a really great way to keep styles across the app consistent and easy to tweak.
Here's my question: is there any way to have the XIB files/Storyboards reflect these text styles? If not, is there any way to implement similar functionality (i.e., have all the styles defined in one place, and have the XIB/Storyboard elements pull from there)? Thanks for reading.
EDIT:
To clarify, here's the desired end-result:
Define some constant text styles like h1, h2, p somewhere in the project. Each text style has its own font, font size, colour, and so on.
Be able to set the style of UILabels in my various views to any of these text styles. This could be done in code, in Interface Builder (e.g., in User Defined Runtime Attributes as Luan suggested), or wherever.
Be able to see the styles applied to each UILabel in Interface Builder / Storyboard without having to run the app every time.

OK, so it turns out this is possible to do! Here's how:
Add a styles class, where you can put all your style info in one place:
import UIKit
class MyStyles: NSObject {
static func fontForStyle(style:String)->UIFont{
switch style{
case "p":
return UIFont.systemFontOfSize(18);
case "h1":
return UIFont.boldSystemFontOfSize(36);
case "h2":
return UIFont.boldSystemFontOfSize(24);
default:
return MyStyle.fontForStyle("p");
}
}
}
Make a subclass of any objects you'd like to implement the styles, say UILabel, and enter the following code:
import UIKit
#IBDesignable class MyLabel: UILabel {
#IBInspectable var style:String="p"{
didSet{self.font=MyStyle.fontForStyle(style)}
}
}
Add a UILabel to your view in Interface Builder and set its class to MyLabel. Then in the Attributes Inspector, you'll see a Style field where you can type h1, h2 or whatever, and the label's font will update right away. Want to change all your h1 labels to have a size of 48? Just update MyStyles and you'll see the changes in your XIBs/Storyboards right away.
This is going to be a huge time-saver, I hope some of you find it useful too!

I don't really get you mean. But you want to set custom "text style" from Interface Builder you can do this. for UILable
1, Create category UILabel + MyCustomStyle.
in UILabel + MyCustomStyle.h
#import <UIKit/UIKit.h>
#interface UILabel (MyCustomStyle)
#property (nonatomic, copy) NSString *mTextStyle;
#end
in UILabel + MyCustomStyle.m
#import "UILabel+MyCustomStyle.h"
#implementation UILabel (MyCustomStyle)
-(NSString *)mTextStyle {
return self.text;
}
-(void)setMTextStyle:(NSString *)txt{
if ([txt isEqualToString:#"h1"]) {
// custom style h1 code
self.font = ...;
}else if ([txt isEqualToString:#"h2"]){
// custom style h2 code
self.font = ...;
}
//... more custom style
}
2, Assign text style in IB

Related

Change UIAppearance of default UILabels only

I'm building an app with a theme switcher and I'd like to use Appearance to switch the textColor of UILabels that have the default color (black -> white).
I assumed this would be possible and created subclasses for non-default labels, so I simply have to target UILabels with either no subclass or that kept the default color.
When I change the appearance with the following :
UILabel.appearance().textColor = UIColor.white
Every single Label, including subclasses and system labels become white, UIAppearance overriding control-level customization. I feel like it makes sense for UIAppearance to be the default state and for any customization to be applied on top of it.
Would there be any way to use UIAppearance to solve this? Or do I have to manually edit every label I have to add a subclass and a custom property?
Thanks for your help!
The only way I found to overcome this problem is to make the textColor variable read only. But it works:
UILabel.appearance().textColor = UIColor.orange
And the custom class:
class MyLabel: UILabel {
override var textColor: UIColor! {
get { return UIColor.blue }
set {}
}
}

Creating utility for label with Icon

I wanted a UILabel with icon.
So i created a utility which will create label, icon and deal with constraints for spacing between them etc.
Now when i use this component in my client code, i again need to create a UILabel in storyboard.
#IBOutlet weak var stateLabel: iconLabel!
...
stateLabel.style = withIcon
and
utility has prototype code:
struct style {
static let withIcon = style(
iconName = x.png,
textColor: white
}
class iconLabel {
createLabel() {
label = UILabel()
addSubview(label)
//deal with constraints
}
createIcon() {
icon = UIImageView
addSubview(icon)
//deal with constraints
}
}
So problem is:
1)I have to have label in my client + in my utility. How to avoid this.
2)If i delete label from my client file, then that label has constraints with other labels in storyboard and i dont understand if i remove it, how can i use my utility.
3)If i dont recreate label in utility, then how can i set constraints between label and icon.
4) If i dont create utility, then every client has to have UIImageView for icon and code for constraints betweein icon and label. So i wanted to create a utility.
Any hints ?
Thanks.
Have a look at this basic tutorial by apple for creating custom views:
Custom controls

UILabel text property when set to nil or "" makes UILabel disappear from view (Swift / Autolayout/ iOS9.1)

I am going through the Stanford Winter 2015 Swift/iOS course and while doing the assignments I run into a behavior I'd like to change.
I use Autolayout as described in the videos (making the display pin to leading and trailing view edges) and the Calculator app "Display" UILabel is fine with an initial value of 0 and whenever the value used to set it (a String) is non-nil and non "".
If it is either nil or "", the entire UILabel disappears. What I am trying to do is to "clear" the display whenever there is no value to display or an incorrect calculation resulted in nil.
Any tips on who to deal with this in general? "Clearing" a UILabel without changing it's on-screen dimensions?
Edit (thanks Rob)
The UILabel has the following constraints
1. Option-click drag-left to containing UIView, selected "leading" something (on commute to work can't check yet for exact wording.
2. Same method as (1) except that the drag is to the right edge and selecting "trailing"
3. Option click-drag up to top of view, select "vertical" menu option.
4. Same as (3) except that drag is to a UIButton underneath the UILabel on the GUI.
With those settings, the label when it contains a number is always visible and (if understand, will color it to verify) stretches across the screen even if the text doesn't.
The layout looks correct in profile and landscape as long as content of UILabel is not empty. If empty, it seems to "shrink to fit" so much that the buttons below get moved up towards the top.
I'm a C++ dev since mid 90s but I have little UI experience and no more than a couple weeks experience in iOS/Swift development.
Thanks!
You can always give the UILabel a min width and min height or constraints that holds the left and right side of the label. That should keep the label from changing it's dimensions to zero.
Use a custom UILabel class assigned in Interface Builder >> Identity inspector >> Custom Class >> Class to override UILabel intrinsic content size.
No need to create any superfluous auto-layout constraints.
Swift:
class UILabelNonCompressible: UILabel
{
private static let NonCompressibleInvisibleContent = " "
override var intrinsicContentSize: CGSize
{
if /* zero-width */ text == nil ? true : text!.isEmpty
{
// prefer mirror-and-calculate over modify-calculate-restore due to KVO
let doppelganger = createCopy()
// calculate for any non-zero -height content
doppelganger.text = UILabelNonCompressible.NonCompressibleInvisibleContent
// override
return doppelganger.intrinsicContentSize
}
else
{
return super.intrinsicContentSize
}
}
}
You will also need "How do copy for UILabel?":
extension UILabel
{
func createCopy() -> UILabel
{
let archivedData = NSKeyedArchiver.archivedData(withRootObject: self)
return NSKeyedUnarchiver.unarchiveObject(with: archivedData) as! UILabel
}
}

Most efficient way to reference 2 different IBOutlets at once (where each one only exists for it's own size class)?

I am currently using unified storyboards (with size classes). For 1 of my views, the difference in design between the wAny hAny and the wRegular hRegular size classes is significant enough that altering the constraints for the subviews based on size class is not enough.
A simplified example: I have a UILabel in the wAny hAny size class called "First_Name_Label". While I wish to reuse this UILabel for the wRegular hRegular size class, the design is too different, so I add a UILabel for the wRegular hRegular size class to replicate the purpose of the "First_Name_Label", and I call it "First_Name_Label 2". The "First_Name_Label" is only installed in the wAny hAny size class, and "First_Name_Label 2" is only installed in the wRegular hRegular size class.
In my code, I want to set the text for the label:
self.First_Name_Label.text = "my first name"
But I need to do it for the other size class as well, and my code would be as follows:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomIpad) {
self.First_Name_Label2.text = "my first name"
} else {
self.First_Name_Label.text = "my first name"
}
This seems very cumbersome, basically doubling my current code. I'm sure there's a more elegant and efficient way to reference these 2 IBOutlets (each belonging to a different size class) at once.
You can't connect an IBOutlet property with more than one object.
But you can use the same tag on those labels and get to them like this:
(UILabel *)[self.view viewWithTag:LABEL_TAG];
Just use optionals:
self.First_Name_Label2?.text = "my first name"
self.First_Name_Label?.text = "my first name"
In this case First_Name_Label and First_Name_Label2 are optional and if it is not initialized the code will just ignore it.
Make use they are declare as optionals in the class as well:
var First_Name_Label:UILabelView?
var First_Name_Label2:UILabelView?
Have you consider using an IBOutletCollection?
e.g.
#property (nonatomic, strong) IBOutletCollection(UILabel) NSArray *First_Name_Labels;
In code, you call:
for (UILabel *First_Name_Label in self.First_Name_Labels) {
First_Name_Label.text = "my first name";
}
This also easily allows you to expand your scope to more size classes.

Update segmented control in ios without change interface

when I update segmented control text, the interface (segment's width) changed and cut some letters.
[segmentedcontoll setTitle:#"test" forSegmentAtIndex:1];
segmentedcontoll.apportionsSegmentWidthsByContent = YES;
How can I solve this ?
EDIT:
It looks like your content has outgrown the dimensions of the standard UISegmentedControl.
If you are okay with smaller font, it's possible to set the entire control to have a smaller font point size, seen here.
Another option is to configure the segments the other supported way.. With images. It's a little bit of a hack, but you can create images on the fly with the UIView Snapshotting API of views/labels configured however you want and set images for each segment instead of using text. This would allow you to create 2 line labels with fixed widths and set images for each section to be images generated from the label as the content changes. More work, but you would still be using the standard class.
The last option, which might work the best for you, is to create some other custom control that does what you would like. After all, UISegmentedControl really is just a nice button container. And it does somewhat seem like you are using the control in a non-standard way - both as a control and an input form section.
Others have gone this route before and created alternatives that you can use.
You can create a separate class as below,
class CustomSegmentedControl: UISegmentedControl {
//code for creating multi line
override func didMoveToSuperview()
{
for segment in subviews
{
for subview in segment.subviews
{
if let segmentLabel = subview as? UILabel
{
segmentLabel.numberOfLines = 0 //just change here the number of lines and check it.
}
}
}
}
}
and create an outlet in your viewcontroller as,
// Initialize
let items = ["Purple", "Green", "New Segment"]
let customSC = CustomSegmentedControl(items: items)
use customSC and do what ever you want to do, similar to segmentedControl object.

Resources