How to resize UITableView in Today Extension - ios

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;
}

Related

Activity shown in custom cell view

I have to create a screen displaying various activity in the user's account such as picture like, uploading images and videos. Just like this-
I thought of using custom TableViewCell for which I need to make two different custom cells in a same table view. I had no problem while making a custom cell for the 2nd activity but am facing problem for the first activity as I have to add an UIImageView for the picture and video. The height of view is not increasing along with UIImage added. Can anyone tell me how can I make two different shapes of custom cells or should I use some other way to show the activities?
You should calculate cell height for different cells. Here is how I do with this:
+(CGFloat)heightForBubbleWithObject:(MessageModel *)object
{
CGSize retSize = object.size;
if (retSize.width == 0 || retSize.height == 0) {
retSize.width = MAX_SIZE;
retSize.height = MAX_SIZE;
}else if (retSize.width > retSize.height) {
CGFloat height = MAX_SIZE / retSize.width * retSize.height;
retSize.height = height;
retSize.width = MAX_SIZE;
}else {
CGFloat width = MAX_SIZE / retSize.height * retSize.width;
retSize.width = width;
retSize.height = MAX_SIZE;
}
return 2 * BUBBLE_VIEW_PADDING + retSize.height;
}
in ios 8 and above put this magical lines and make sure the constraints are properly given
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
there is a wonderful answer about this please refer it for the details Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Change Row Height size of Table View between iPad and iPhone

I know thinks about size class, but here I don't know how to do, on my menu I wan't to have a bigger row height size on iPad than iPhone.
Here I can set the row height:
But I don't see how to use size class here to have a different height between different devices.
If doing it in the code is an option, you could do something like this in viewDidLoad:
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
tableView.rowHeight = 100;
}
else {
tableView.rowHeight = 44;
}
it should work on iOS 3.2 and above, so basically anything you can realistically target.
In Swift Use:
if(UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad){
return 500
}
else {
return 200
}

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 set the height of a Today Widget Extension?

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;}

iOS - my heightForRowAtIndexPath implementation causes performance issues, why?

I use a table view where I override heightForRowAtIndexPath. When I run the Xcode profiler I found the following:
I want to calculate the height based one object's property. I use the same table view for two kind of objects, User and Post. My implementation currently looks like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text;
Post *cellPost;
User *cellUser;
if (_pageType == FOLLOWERS || _pageType == FOLLOWING) {
cellUser = [self.users objectAtIndex:indexPath.row];
text = cellUser.userDescription.text;
if (text == nil) {
return 70;
}
} else {
cellPost = [self.fetchedResultsController objectAtIndexPath:indexPath];
text = cellPost.text;
}
CGSize boundingSize = CGSizeMake(245, CGFLOAT_MAX);
CGSize requiredSize = [text sizeWithFont:[UIFont fontWithName:#"TisaMobiPro" size:15]
constrainedToSize:boundingSize
lineBreakMode:NSLineBreakByWordWrapping];
CGFloat textHeight = requiredSize.height;
CGFloat cellHeight = textHeight + 40;
if ([cellPost.text isEqualToString:self.post.text]) {
cellHeight += 44;
if (cellHeight < 114) {
cellHeight = 114;
}
} else {
if (cellHeight < 70) {
cellHeight = 70;
}
}
if (cellPost.repostedBy != nil && cellPost.youReposted.boolValue == NO && _pageType != CONVERSATION) {
cellHeight += 27;
}
return cellHeight;
}
If I remove most of the code and only have, e.g. return 100 the tableView's scrolling performance is much improved. Can you spot or give suggestions on something in my implantation that could cause this performance issue?
Heights are calculated before the – tableView:cellForRowAtIndexPath: is called and for all the number of cells.
If you are deploying on iOS7 you can use the - tableView:estimatedHeightForRowAtIndexPath:, using that method you can defer this calculation while scrolling the tableview. That means that the method tableView:heightForRowAtIndexPath: is called basically when a cell is displayed. The estimation could be a fixed number close to the average of your TVC or based on some math faster than the one that you implemented.
Thanks to the estimation the TV can set its content size an scroll bar height and doesn't need to calculate each height for 100 of rows in one shot. The payback could be a lag during scrolling because the TV is calculating the actual row height.
If you are targeting iOS7 you can use the NSTableView property: estimatedRowHeight.
From the Apple docs:
Providing a nonnegative estimate of the height of rows can improve the
performance of loading the table view. If the table contains variable
height rows, it might be expensive to calculate all their heights when
the table loads. Using estimation allows you to defer some of the cost
of geometry calculation from load time to scrolling time.
Also:
Every time a
table view is displayed, it calls tableView:heightForRowAtIndexPath:
on the delegate for each of its rows
If you have to use tableView:heightForRowAtIndexPath: cache the heights as #Waine suggests.

Resources