19

I'm trying to find out how to calculate the left margin in the Today extension main view to align the contents to the rest of the Today view labels.

Here's an example with a clean Xcode project using a Today extensions (I've added color to the view backgrounds and drawn a dashed red line to illustrate where I'd like to align the Hello World UILabel).

The result in iPhone 6 Plus simulator (left side landscape, right side portrait) can be found from the image below:

enter image description here

In the image, notice that the green main view left boundary is placed differently related to the app name UILabel "testi2". It also seems that the red line - main views left border alignment is different in each device: iPhone 5x, iPhone 6 and iPads.

The behavior can be reproduced using a clean Xcode project (I'm using Xcode 6.1.1, iOS 8.1 and Swift):

  1. Create an empty Xcode project (A single-view application)
  2. Add a new Target: Extensions > Today extension
  3. From the Today extension group, find MainInterface.storyboard and make the main view background green and Hello world UILabel background red: enter image description here

How do I align the the Hello World UILabel (red background) to the dashed line? Or how do I align the main view (green background) to the dashed line?

Markus Rautopuro
  • 7,997
  • 6
  • 47
  • 60
  • Image is broken, at least here. "You've requested a page on a website (i.stack.imgur.com) that is on the CloudFlare network. CloudFlare is currently unable to resolve your requested domain (i.stack.imgur.com)." – Duncan Babbage Dec 28 '14 at 18:04
  • Actually, this seems to be a StackOverflow issue. Just noticed another broken image which is a site-related image. Ignore. – Duncan Babbage Dec 28 '14 at 18:05
  • Ok, here's another link to the image anyway: http://ibin.co/w800/1mDY3ckx95h7 – Markus Rautopuro Dec 28 '14 at 18:07
  • I would have thought the Today view would correctly align these if you didn't set any margin, though I have not tested this. What is the code that is producing the results in the screenshot? – Duncan Babbage Dec 28 '14 at 20:26
  • See the edited question: just by adding color to the default Today extension Interface Builder template produces weird alignment in portrait and landscape modes. – Markus Rautopuro Dec 29 '14 at 18:54
  • As @dehlen pointed out, there's discussion about the margins in this thread: http://stackoverflow.com/questions/26025139/ios-8-today-widget-alignment-issue, however the solution provided there requires finding out the magic pixel values for each device and orientation. – Markus Rautopuro Jan 02 '15 at 11:01

3 Answers3

17

Did you try this one ?

Objective-C:

- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
    return UIEdgeInsetsZero;
}

Swift:

func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
        return UIEdgeInsetsZero
}

Otherwise it looks like you will have to set them manually according to this So-Thread:

EDIT:

Looks like this method is deprecated and is not called for devices running >= iOS10. However I could not find any documentation on an alternative. If you have any information on this please add to this post so everyone can profit. Just make sure when using this function that it will not be called on >= iOS10.

Source: Apple Documentation

Community
  • 1
  • 1
dehlen
  • 7,325
  • 4
  • 43
  • 71
  • Thanks, let me try that. I'm looking for a solution that *does not* require entering magic pixel values for each device and orientation. – Markus Rautopuro Jan 02 '15 at 10:33
  • 1
    The example (I'm using Swift, as mentioned in the question, btw) you provided indeed takes out *all the margins*. However, it doesn't answer how to calculate the position (red striped line in my screenshot) of the widget name `UILabel`. – Markus Rautopuro Jan 02 '15 at 10:59
6

I tried using the left value of the defaultMarginInset of -widgetMarginInsetsForProposedMarginInsets with mixed results: On the iPhone 5S screen dimensions, it can be used to get the same inset as the default calendar widget of iOS (note that the right margin of the time label aligns with the blue line):

5S Portrait

5S Landscape

On the iPhone 6, you get similar results, i.e. it also aligns. However, on iPhone 6 Plus, the calendar widget somehow scales the inset:

6P Portrait

6P Landscape

Note that in the landscape version, neither the time nor the lines align to anything.

In conclusion, I would say that you can safely use defaultMarginInset.left to get decent results.


Swift code:

class TodayViewController: UIViewController, NCWidgetProviding {

    var defaultLeftInset: CGFloat = 0
    var marginIndicator = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        marginIndicator.backgroundColor = UIColor.whiteColor()
        view.addSubview(marginIndicator)
    }

    func widgetMarginInsetsForProposedMarginInsets(var defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
        defaultLeftInset = defaultMarginInsets.left

        defaultMarginInsets.left = 0
        return defaultMarginInsets
    }

    func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
        marginIndicator.frame = CGRectMake(defaultLeftInset, 0, 10, view.frame.size.height)
        completionHandler(NCUpdateResult.NewData)
    }
}
fabian789
  • 8,348
  • 4
  • 45
  • 91
  • It's decent, but it's not exact. Apple is doing the aligning somehow, I wonder how. I bet they don't have a fixed set of pixel values for every device and orientation. – Markus Rautopuro Jan 02 '15 at 11:42
  • I would not be so sure about that. Also, as you see on the 6 Plus, it does not really align to anything. Furthermore if you compare insets of the Reminders widget to the calendar widget, you will notice that it does not align anything at all. It seems to me that they are not so strict about that, unfortunately... – fabian789 Jan 02 '15 at 11:55
  • In iPhone 6 Plus landscape, the text "You have no events scheduled for tomorrow." is perfectly aligned with "Tomorrow" widget title ("Tomorrow Summary" Today widget by Apple). – Markus Rautopuro Jan 02 '15 at 13:49
  • Ah, I did not notice that. Still, Apple might also have hardcoded some value they don't expose publicly: Like the height of the keyboard. – fabian789 Jan 02 '15 at 15:14
4

My temporary solution goes as follows. I'm using constant values for setting the left and top margins. This way I can align the content exactly like it's, for example, in Tomorrow Summary widget.

First some helper methods for determining the device type (adapted from this answer):

struct ScreenSize {
    static let SCREEN_WIDTH = UIScreen.mainScreen().bounds.size.width
    static let SCREEN_HEIGHT = UIScreen.mainScreen().bounds.size.height
    static let SCREEN_MAX_LENGTH = max(ScreenSize.SCREEN_WIDTH,
        ScreenSize.SCREEN_HEIGHT)
    static let SCREEN_MIN_LENGTH = min(ScreenSize.SCREEN_WIDTH,
        ScreenSize.SCREEN_HEIGHT)
}

struct DeviceType {
    static let iPhone4 =  UIDevice.currentDevice().userInterfaceIdiom == .Phone
        && ScreenSize.SCREEN_MAX_LENGTH < 568.0
    static let iPhone5 = UIDevice.currentDevice().userInterfaceIdiom == .Phone
        && ScreenSize.SCREEN_MAX_LENGTH == 568.0
    static let iPhone6 = UIDevice.currentDevice().userInterfaceIdiom == .Phone
        && ScreenSize.SCREEN_MAX_LENGTH == 667.0
    static let iPhone6Plus = UIDevice.currentDevice().userInterfaceIdiom == .Phone
        && ScreenSize.SCREEN_MAX_LENGTH == 736.0
    static let iPad = UIDevice.currentDevice().userInterfaceIdiom == .Pad
}

Then using widgetMarginInsetsForProposedMarginInsets: as suggested I overwrite the left and top insets as follows:

func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets)
        -> UIEdgeInsets {
    var insets = defaultMarginInsets
    let isPortrait = UIScreen.mainScreen().bounds.size.width
        < UIScreen.mainScreen().bounds.size.height

    insets.top = 10.0
    if DeviceType.iPhone6Plus {
        insets.left = isPortrait ? 53.0 : 82.0
    } else if DeviceType.iPhone6 {
        insets.left = 49.0
    } else if DeviceType.iPhone5 {
        insets.left = 49.0
    } else if DeviceType.iPhone4 {
        insets.left = 49.0
    } else if DeviceType.iPad {
        insets.left = isPortrait ? 58.0 : 58.0
    }

    return insets
}

However, this is not the solution I'm looking for - I'd like to get rid of hardcoding per-device-per-orientation pixel values.

Community
  • 1
  • 1
Markus Rautopuro
  • 7,997
  • 6
  • 47
  • 60