How to set the height of a Today Widget Extension? - ios

How can i change the height of my App's Today Extension in the Notification Center?
I tried it with the Interface Builder and with Code,
the Interface Builder Displays the View with height 600, but it's not applying this height on the device.
It seems I can't get it bigger than some 80 pixels...

In your widget UIViewController.m (Objective-C):
self.preferredContentSize = CGSizeMake(0, 200);
Will make your widget have a height of 200.
Note that the width will have no affect on the view, as widgets must fit in the exact width of notification center, which is handled automagically.
Also, if you want to animate changes in the height of your view, you can implement (Objective-C):
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
in your view controller using -animateAlongsideTransition:completion:
The answer was a bit hidden; you had to click around in the documentation sidebar to eventually find this fantastic document.
Another way is to use auto-layout constraints to constrain your view's height.

Widgets have their heights adjusted by the system. If you have defined your height using constraints this will be automatically adjusted as required. If you're using explicit layout you can request a new height by modifying the preferredContentSize of your widget.
Note that you have no guarantee notification center will respect your height request: it may be adjusted automatically, it may be adjusted but not to the exact height you want, or it may not be honored at all.
The best way to develop a widget is to use auto-layout constraints to set your height values, that way your widget will adapt to different heights with ease.

Since iOS 10 extension's height is 110 pixels. You should use new protocol method widgetActiveDisplayModeDidChange:withMaximumSize: to extend extension size (Objective-C):
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode
withMaximumSize:(CGSize)maxSize {
if (activeDisplayMode == NCWidgetDisplayModeExpanded) {
self.preferredContentSize = CGSizeMake(maxSize.width, 600.0);
} else if (activeDisplayMode == NCWidgetDisplayModeCompact) {
self.preferredContentSize = maxSize;
}
}
Also you may need to call setWidgetLargestAvailableDisplayMode: on your extension context in today view controller's viewDidLoad method like this (Objective-C):
if ([self.extensionContext respondsToSelector:#selector(setWidgetLargestAvailableDisplayMode:)]) { // iOS 10+
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded];
} else {
self.preferredContentSize = CGSizeMake(0, 600.0); // iOS 10-
}
This thread may be helpful https://forums.developer.apple.com/thread/48930

The best way is of course Autolayout but by default there are margins which you can control like this
func widgetMarginInsetsForProposedMarginInsets
(defaultMarginInsets: UIEdgeInsets) -> (UIEdgeInsets) {
return UIEdgeInsetsZero
}

There are two ways to display Today extension:
Compact Mode (fixed height for Widget)
Expand Mode (Variable height for Widget)
Whatever code you do to change the height of extension in Compact mode will not make any difference. So you need to change the mode from compact to Expand Mode.
// 1. Load This in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded
}
// 2. Implement another widget protocol
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize){
if (activeDisplayMode == NCWidgetDisplayMode.compact) {
self.preferredContentSize = maxSize;
}
else {
self.preferredContentSize = CGSize(width: 0, height: 200);
}
}
You can refer WWDC for more updates about App extensions

Today widget default UIEdgeInsets defaultMarginInsets
(UIEdgeInsets) defaultMarginInsets = (top = 0, left = 44, bottom = 39, right = 0)
You should add this method
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(0, 44, 0, 0);
return edgeInsets;}

Related

How to add Image on UIButton on right side and contentMode don't working with image?

When i added image (by storyboard) to a button image content Mode not working .When i fix it(iPhone) by image insets and title insets on another device (iPad) its lose expect ratio ? how can i fix it ??
You can set Semantic to Force Right-To-Left of UIButton to set image at right side. Default Semantic value Unspecified.
The property "Semantic" in the storyboard is a rule which allows the iOS to know if the view should be flipped or not. - Apple's Documentations
Try this code and modify EdgeInset as you want, and assign your outlet as DropdownButton instead of IUButton
class DropdownButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
self.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
if self.imageView?.image != nil {
// Move icon to right side
self.imageEdgeInsets = UIEdgeInsets(
top: 0,
left: self.bounds.size.width - self.imageView!.image!.size.width - 10,
bottom: 0,
right: 0)
// Move title to left side
self.titleEdgeInsets = UIEdgeInsetsMake(5, -self.imageView!.frame.size.width + 10, 5, 5)
}
}
}
You may try to set it programmatically. For example if the device is iPad or iPhone set the insets programatically. For example button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0). try this code in viewWillAppear or viewDidLoad. I am using 0 just for the sake of understanding, use your own measured values.

How can I enforce an UILabel to be wider than it should be, by 5 points?

I have an UILabel, it has background color as green. But It wraps the text very closely. I need the green background to be little wider than the text, approx 5 points. How can I achieve this?
Details
Label is created in storyboard and has horizontal spacing constraints. You can ignore the constraints as I guess, because I have handled it for any width.
Suggested Requirement
I guess I need a method which I can override in my UILabel's subclass, to increase its width by 5 points. Anybody knows which method I shoul override?
You may try to subclassing of UILabel class and overriding below method -
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {0, 5, 0, 5};
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
Hope this will be help you !
You can achieve it by calling sizeToFit first, it will shrink the frame and fit to text, then you can increase width by 10 pixels and remember to keep it center aligned.
Hope it helps.. Let me know... :)
Finally I solved my problem! It works great like this -
Make a subclass of UILabel and override the intrinsicContentSize and sizeThatFits to achieve what you want.
So, something like:
- (CGSize) intrinsicContentSize
{
return [self addHorizontalPadding:[super intrinsicContentSize]];
}
- (CGSize)sizeThatFits:(CGSize)size
{
return [self addHorizontalPadding:[super intrinsicContentSize]];
}
- (CGSize)addHorizontalPadding:(CGSize)size
{
if (size.width > 0)
return CGSizeMake(size.width + (2*kSomeHorizontalPaddingValue), size.height);
else
return size;
}
Note that this only touches the horizontal padding, but can obviously be modified to add vertical padding as well.
I think below code will be working for you
CGFloat width = [label.text sizeWithFont:[UIFont systemFontOfSize:22 ]].width;
label.frame = CGRectMake(point.x, point.y, width+5,height);

Size class to identify iPhone 6 and iPhone 6 plus portrait

How to uniquely identify iPhone 6 and iPhone 6 plus portrait screens using size classes?
My App looks good in iPhone 4 and iPhone 5 but the same looks with lots of empty spaces in iPhone 6 and 6 plus because of screen sizes. Though am using auto layout i can't increase the font size or view size only for iPhone 6 and 6 plus alone. I knew that we can change the font size and view size using size classes. but in my case don't know what to do.
Am using xCode 6.1 and my app supports from iOS 7 to latest iOS 8.1. Am expecting solution only in storyboards as am doing my UI designs fully in storyboard. If storyboard has limited functionality to achieve my needs please let me know how to achieve the same with code through out the app?
Another option to adjust the font size according to the iPhone type, is to use 'User Defined Runtime Attributes'.
Define an extension to UILabel:
extension UILabel {
var adjustFontToRealIPhoneSize: Bool {
set {
if newValue {
var currentFont = self.font
var sizeScale: CGFloat = 1
let model = UIDevice.CurrentDevice().modelName()
if model == "iPhone 6" {
sizeScale = 1.3
}
else if model == "iPhone 6 Plus" {
sizeScale = 1.5
}
self.font = currentFont.fontWithSize(currentFont.pointSize * sizeScale)
}
}
get {
return false
}
}
}
In order to determine the current model name, please refer to the following answer: https://stackoverflow.com/a/26962452/4165128
On the storyboard, select the label you wish to adjust, open the right pane, select the identity inspector, and add the 'adjustFontToRealIPhoneSize' property to the list of user defined runtime attributes (with type 'Boolean' and checkbox checked).
Do the same for each label you wish to adjust (copy & paste surprisingly works here).
Use Compact width and Regular Height in storyboard
Add layout constraint of hight and width relative with super view by adding multiplier.
Let's say you have image view which has size half the super view then add multiplier 0.5.
Check out adjustsFontSizeToFitWidth # UILabel Class Reference. This will allow you to do some nice adjustments based on the different devices.
label.adjustsFontSizeToFitWidth = YES;
I don't have too much idea of sizeClass for different font size in different iPhone devices but I figured out with this solution.
Add this method to your utility class.
Just pass your super view to this method, this method is recursive so if you pass self.view than all subviews are set.
Change your font size as you need.
-(void)setAllFonts:(UIView *)view
{
CGFloat fontSizeDiff = 0.0;
if (IS_IPHONE_6)
{
fontSizeDiff = 1;
}
else if (IS_IPHONE_6PLUS)
{
fontSizeDiff = 2;
}
for (UIView *vw in [view subviews])
{
if ([vw isKindOfClass:[UILabel class]] || [vw isKindOfClass:[UIButton class]])
{
if ([vw isKindOfClass:[UILabel class]])
{
UIFont *font = [(UILabel *)vw font];
[(UILabel *)vw setFont:[UIFont fontWithName:font.fontName size:font.pointSize+fontSizeDiff]];
}
else
{
UIFont *font = [(UIButton *)vw titleLabel].font;
[(UIButton *)vw titleLabel].font = [UIFont fontWithName:font.fontName size:font.pointSize+fontSizeDiff];
}
}
else if ([vw isKindOfClass:[UIView class]] || [vw isKindOfClass:[UIScrollView class]])
{
[self setAllFonts:vw];
}
}
}
Swift Version of #iBhaviks answer :
func getFontScaleForDevice() -> CGFloat {
var sizeScale = CGFloat(1.0)
if DeviceType.IS_IPHONE_6 {
sizeScale = 1.2
} else if DeviceType.IS_IPHONE_6P {
sizeScale = 1.4
} else if DeviceType.IS_IPAD {
sizeScale = 1.4
}
return sizeScale
}
func setAllFonts(targetView:UIView, scale:CGFloat) {
for vw in targetView.subviews {
if let vl = vw as? UILabel {
vl.font = vl.font.fontWithSize(round(vl.font.pointSize * scale))
} else if let vb = vw as? UIButton, vbl = vb.titleLabel {
vbl.font = vbl.font.fontWithSize(vbl.font.pointSize * scale)
} else if vw.subviews.count > 0 {
setAllFonts(vw, scale: scale)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
let sizeScale = getFontScaleForDevice()
if sizeScale > CGFloat(1.0) {
setAllFonts(view, scale: sizeScale)
}
view.layoutIfNeeded()
}
Maybe you could set the perfect font size for the big screens, and then set the Autoshrink to minimum font size with the perfect size for the small screens, in that way you can have a dynamic font size without coding.
You will have to set the constraints for the label to adjust its size with the screen size anyway.
Hope this help
After struggling lot just found of something for my need so posting the same for the future readers.
As of now there is no way to uniquely identify the iPhone 6 model portraits using Size classes. However you can use compact width and regular height to design for all iPhones portrait screens
To change font size you have to identify which iPhone the app currently running on using code and set the font size based on the same
For my requirement - same layout for all the screen sizes - use multiplier in widths and heights constrains. Check Jignesh answers for this question to know more about it.
EDIT 24-SEP-2015
I recently found a way to customize your size class by using UITraitColleection only for iPhone 6 plus so you don't need to write much of code. i hope this link will help someone in future.
Bit late to this but I needed a label that would scale up to any device and used this method.
Create a new subclass of UILabel and in the .h file put this…
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface OTHD_ScalableLabel : UILabel
#property (nonatomic, assign) IBInspectable CGFloat fontScale;
#end
and in the .m file put this…
#import "OTHD_ScalableLabel.h"
#implementation OTHD_ScalableLabel
- (void) layoutSubviews
{
[super layoutSubviews];
if( self.fontScale < 0.1 || self.fontScale > 1.0 ) self.fontScale = 1.0;
CGFloat height = CGRectGetHeight(self.bounds) * self.fontScale;
if( height ) self.font = [UIFont fontWithName:self.font.fontName size:height];
}
#end
You can then just change your class in IB and using IBInspectable you will be able to scale the label up or down. Obviously, for the pedantics out there, this is not a good idea for general use but there are some cases where you might need, for example, a large label that displays full screen on an iPhone as well as full screen on an iPad.
Here is an example of a function that I use to toggle between two different font size at runtime. It decides which font to use based on the horizontal size class - which essentially splits devices into two groups "iPad" and "iPhone". Here is a good refernce on whch devices belong to which size classes: http://useyourloaf.com/blog/size-classes/
iPad and Up
iPhone Plus and Down
func chooseFont(compactFont: UIFont, regularFont: UIFont) -> UIFont {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.window!.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.compact ? compactFont : regularFont
}
Based on your screen shot I would suggest using Dynamic Type. That way the user is in charge of the size of the text.

How to resize UITableView in Today Extension

I am building a Today Extension for both iPhone and iPad and I am having the problem that I am not able to use different Storyboards for iPad and iPhone.
I am able to resize the width of the Extensions view with
if isIpad() {
width = 550
} else {
width = 275
}
preferredContentSize = CGSizeMake(width, 200)
but I am not able to resize the tableViews Frame with this code
tableView.frame = view.frame
after the code above.
Does anyone know how to make a universal Today Extension?
You can use a UITableViewController or you can use autoLayout to pin the tableView to the container. In both cases the width will be adjusted automatically.
For the height of the widget use something like this: (self.rows should be NSArray containing the rows)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.rows) {
CGSize size = self.preferredContentSize;
size.height = self.rows.count * 44.0f;
self.preferredContentSize = size;
return self.rows.count;
}
return 0;
}

Add left/right horizontal padding to UILabel

I need to create a UILabel with a background color, and I'd like to add some left/right leading/trailing horizontal padding.
But every solution I've found seems like a nasty hack.
What is the 'standard' way to achieve this from iOS 5 forward?
A screenshot to illustrate my scenario:
For a full list of available solutions, see this answer: UILabel text margin
Try subclassing UILabel, like #Tommy Herbert suggests in the answer to [this question][1]. Copied and pasted for your convenience:
I solved this by subclassing UILabel and overriding drawTextInRect: like this:
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {0, 5, 0, 5};
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
The most important part is that you must override both intrinsicContentSize() and drawTextInRect() in order to account for AutoLayout:
var contentInset: UIEdgeInsets = .zero {
didSet {
setNeedsDisplay()
}
}
override public var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + contentInset.left + contentInset.right, height: size.height + contentInset.top + contentInset.bottom)
}
override public func drawText(in rect: CGRect) {
super.drawText(in: UIEdgeInsetsInsetRect(rect, contentInset))
}
add a space character too the string. that's poor man's padding :)
OR
I would go with a custom background view but if you don't want that, the space is the only other easy options I see...
OR write a custom label. render the text via coretext
#define PADDING 5
#interface MyLabel : UILabel
#end
#implementation MyLabel
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = UIEdgeInsetsMake(0, PADDING, 0, PADDING);
CGRect rect = UIEdgeInsetsInsetRect(rect, insets);
return [super drawTextInRect:rect];
}
- (CGRect)textRectForBounds:(CGRect)bounds
limitedToNumberOfLines:(NSInteger)numberOfLines
{
CGSize size = CGSizeMake(999, 999);
CGRect rect = [self.attributedText
boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return CGRectInset(rect, -PADDING, 0);
}
#end
UIView* bg = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, 70)];
bg.backgroundColor = [UIColor blackColor];
UILabel* yourLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, y, yourWidth, yourHeight)];
[bg addSubview:yourLabel];
[self addSubview:bg];
Sometimes it's convenient to use UNICODE partial spaces to achieve alignment while prototyping. This can be handy in prototyping, proof-of-concept, or just to defer implementation of graphics algorithms.
If you use UNICODE spaces for convenience, be aware that at least one of the UNICODE spaces has a size based on the font it is displayed from, specifically the actual space character itself (U+0020, ASCII 32)
If you're using the default iOS system font in a UILabel, the default System font characteristics could change in a subsequent iOS release and suddenly introduce an unwanted misalignment by changing your app's precise spacing. This can and does happen, for example the "San Francisco" font replaced a previous iOS system font in an iOS release.
UNICODE easy to specify in Swift, for example:
let six_per_em_space = "\u{2006}"
Alternatively, cut/paste the space from an HTML page directly into the UILabel's text field in Interface Builder.
Note: Attached pic is a screenshot, not HTML, so visit the linked page if you want to cut/paste the space.
I had a couple of issues with the answers here, such as when you added in the padding, the width of the content was overflowing the box and that I wanted some corner radius. I solved this using the following subclass of UILabel:
#import "MyLabel.h"
#define PADDING 8.0
#define CORNER_RADIUS 4.0
#implementation MyLabel
- (void)drawRect:(CGRect)rect {
self.layer.masksToBounds = YES;
self.layer.cornerRadius = CORNER_RADIUS;
UIEdgeInsets insets = {0, PADDING, 0, PADDING};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
- (CGSize) intrinsicContentSize {
CGSize intrinsicSuperViewContentSize = [super intrinsicContentSize] ;
intrinsicSuperViewContentSize.width += PADDING * 2 ;
return intrinsicSuperViewContentSize ;
}
#end
Hope that's helpful to someone! Note that if you wanted padding on the top and bottom, you would need to change this lines:
UIEdgeInsets insets = {0, PADDING, 0, PADDING};
To this:
UIEdgeInsets insets = {PADDING, PADDING, PADDING, PADDING};
And add this line underneath the similar one for width:
intrinsicSuperViewContentSize.height += PADDING * 2 ;
Swift 5
Create below class file and set it to your label as custom class name through storyboard. That's it.
class PaddingLabel: UILabel {
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0)//CGRect.inset(by:)
super.drawText(in: rect.inset(by: insets))
}
}
If you want to add padding to UILabel but not want to subclass it you can put your label in a UIView and give paddings with autolayout like:
Result:
One thing I did to overcome this issue was to use a UIButton instead of a UILabel. Then in the Attributes Inspector of the Interface Builder, I used the Edge for the Title as the padding.
If you do not attach the button to an action, when clicked it will not get selected but it will still show the highlight.
You can also do this programmatically with the following code:
UIButton *mButton = [[UIButton alloc] init];
[mButton setTitleEdgeInsets:UIEdgeInsetsMake(top, left, bottom, right)];
[mButton setTitle:#"Title" forState:UIControlStateNormal];
[self.view addSubView:mButton];
This approach gives the same result but sometimes it did not work for some reason that I did not investigate since if possible I use the Interface Builder.
This is still a workaround but it works quite nicely if the highlight doesn't bother you. Hope it is useful
Subclass UILabel and override drawTextInRect: like this:
- (void)drawTextInRect:(CGRect)rect
{
UIEdgeInsets insets = {0, 10, 0, 0};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
Installation with CocoaPods
pod 'PaddingLabel', '1.2'
Change your UILabel class to PaddingLabel ###
Specify padding
If you need a more specific text alignment than what adding spaces to the left of the text provides, you can always add a second blank label of exactly how much of an indent you need.
I've got buttons with text aligned left with an indent of 10px and needed a label below to look in line.
It gave the label with text and left alignment and put it at x=10 and then made a small second label of the same background color with a width = 10, and lined it up next to the real label.
Minimal code and looks good.
Just makes AutoLayout a little more of a hassle to get everything working.

Resources