Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #import "ios/chrome/browser/ui/uikit_ui_util.h" | |
| 6 | |
| 7 #import <Accelerate/Accelerate.h> | |
| 8 #import <Foundation/Foundation.h> | |
| 9 #import <QuartzCore/QuartzCore.h> | |
| 10 #import <UIKit/UIKit.h> | |
| 11 #include <cmath> | |
| 12 | |
| 13 #include "base/logging.h" | |
| 14 #include "base/ios/ios_util.h" | |
| 15 #include "base/mac/foundation_util.h" | |
| 16 #include "ios/chrome/browser/ui/ui_util.h" | |
| 17 #include "ios/chrome/browser/ui/ui_util.h" | |
| 18 #include "ui/base/l10n/l10n_util.h" | |
| 19 #include "ui/base/l10n/l10n_util_mac.h" | |
| 20 #include "ui/gfx/scoped_cg_context_save_gstate_mac.h" | |
| 21 #include "ui/ios/uikit_util.h" | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 // Linearly interpolate between |a| and |b| by fraction |f|. Satisfies | |
| 26 // |lerp(a,b,0) == a| and |lerp(a,b,1) == b|. | |
|
sdefresne
2015/01/13 15:19:25
nit: s/lerp/Lerp/g
| |
| 27 CGFloat Lerp(CGFloat a, CGFloat b, CGFloat fraction) { | |
| 28 return a * (1.0f - fraction) + b * fraction; | |
| 29 } | |
| 30 | |
| 31 // Gets the RGBA components from a UIColor in RBG or monochrome color space. | |
| 32 void GetRGBA(UIColor* color, CGFloat* r, CGFloat* g, CGFloat* b, CGFloat* a) { | |
| 33 switch (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor))) { | |
| 34 case kCGColorSpaceModelRGB: { | |
| 35 BOOL success = [color getRed:r green:g blue:b alpha:a]; | |
| 36 DCHECK(success); | |
| 37 return; | |
| 38 } | |
| 39 case kCGColorSpaceModelMonochrome: { | |
| 40 const size_t componentsCount = | |
| 41 CGColorGetNumberOfComponents(color.CGColor); | |
| 42 DCHECK(componentsCount == 1 || componentsCount == 2); | |
| 43 const CGFloat* components = CGColorGetComponents(color.CGColor); | |
| 44 *r = components[0]; | |
| 45 *g = components[0]; | |
| 46 *b = components[0]; | |
| 47 *a = componentsCount == 1 ? 1 : components[1]; | |
| 48 return; | |
| 49 } | |
| 50 default: | |
| 51 NOTREACHED() << "Unsupported color space."; | |
| 52 return; | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 } // namespace | |
| 57 | |
| 58 void SetA11yLabelAndUiAutomationName(UIView* element, | |
| 59 int idsAccessibilityLabel, | |
| 60 NSString* englishUiAutomationName) { | |
| 61 [element setAccessibilityLabel:l10n_util::GetNSString(idsAccessibilityLabel)]; | |
| 62 [element setAccessibilityIdentifier:englishUiAutomationName]; | |
| 63 } | |
| 64 | |
| 65 void GetSizeButtonWidthToFit(UIButton* button) { | |
| 66 // Resize the button's width to fit the new text, but keep the original | |
| 67 // height. sizeToFit appears to ignore the image size, so re-add the size of | |
| 68 // the button's image to the frame width. | |
| 69 CGFloat buttonHeight = [button frame].size.height; | |
| 70 CGFloat imageWidth = [[button imageView] frame].size.width; | |
| 71 [button sizeToFit]; | |
| 72 CGRect newFrame = [button frame]; | |
| 73 newFrame.size.height = buttonHeight; | |
| 74 newFrame.size.width += imageWidth; | |
| 75 [button setFrame:newFrame]; | |
| 76 } | |
| 77 | |
| 78 void TranslateFrame(UIView* view, UIOffset offset) { | |
| 79 if (!view) | |
| 80 return; | |
| 81 | |
| 82 CGRect frame = [view frame]; | |
| 83 frame.origin.x = frame.origin.x + offset.horizontal; | |
| 84 frame.origin.y = frame.origin.y + offset.vertical; | |
| 85 [view setFrame:frame]; | |
| 86 } | |
| 87 | |
| 88 UIFont* GetUIFont(int fontFace, bool isBold, CGFloat fontSize) { | |
| 89 NSString* fontFaceName; | |
| 90 switch (fontFace) { | |
| 91 case FONT_HELVETICA: | |
| 92 fontFaceName = isBold ? @"Helvetica-Bold" : @"Helvetica"; | |
| 93 break; | |
| 94 case FONT_HELVETICA_NEUE: | |
| 95 fontFaceName = isBold ? @"HelveticaNeue-Bold" : @"HelveticaNeue"; | |
| 96 break; | |
| 97 case FONT_HELVETICA_NEUE_LIGHT: | |
| 98 // FONT_HELVETICA_NEUE_LIGHT does not support Bold. | |
| 99 DCHECK(!isBold); | |
| 100 fontFaceName = @"HelveticaNeue-Light"; | |
| 101 break; | |
| 102 default: | |
| 103 NOTREACHED(); | |
| 104 fontFaceName = @"Helvetica"; | |
| 105 break; | |
| 106 } | |
| 107 return [UIFont fontWithName:fontFaceName size:fontSize]; | |
| 108 } | |
| 109 | |
| 110 void AddBorderShadow(UIView* view, CGFloat offset, UIColor* color) { | |
| 111 CGRect rect = CGRectInset(view.bounds, -offset, -offset); | |
| 112 CGPoint waypoints[] = { | |
| 113 CGPointMake(rect.origin.x, rect.origin.y), | |
| 114 CGPointMake(rect.origin.x, rect.origin.y + rect.size.height), | |
| 115 CGPointMake(rect.origin.x + rect.size.width, | |
| 116 rect.origin.y + rect.size.height), | |
| 117 CGPointMake(rect.origin.x + rect.size.width, rect.origin.y), | |
| 118 CGPointMake(rect.origin.x, rect.origin.y)}; | |
| 119 int numberOfWaypoints = sizeof(waypoints) / sizeof(waypoints[0]); | |
| 120 CGMutablePathRef outline = CGPathCreateMutable(); | |
| 121 CGPathAddLines(outline, NULL, waypoints, numberOfWaypoints); | |
| 122 view.layer.shadowColor = [color CGColor]; | |
| 123 view.layer.shadowOpacity = 1.0; | |
| 124 view.layer.shadowOffset = CGSizeZero; | |
| 125 view.layer.shadowPath = outline; | |
| 126 CGPathRelease(outline); | |
| 127 } | |
| 128 | |
| 129 // TODO(pkl): The implementation of this has some duplicated code with | |
| 130 // AddBorderShadow and ToolsPopupView newPathForRect:withRadius:withArrow:. | |
| 131 // There is an opportunity to refactor them into a common shadow library. | |
| 132 void AddRoundedBorderShadow(UIView* view, CGFloat radius, UIColor* color) { | |
| 133 CGRect rect = view.bounds; | |
| 134 CGMutablePathRef path = CGPathCreateMutable(); | |
| 135 CGFloat minX = CGRectGetMinX(rect); | |
| 136 CGFloat midX = CGRectGetMidX(rect); | |
| 137 CGFloat maxX = CGRectGetMaxX(rect); | |
| 138 CGFloat minY = CGRectGetMinY(rect); | |
| 139 CGFloat midY = CGRectGetMidY(rect); | |
| 140 CGFloat maxY = CGRectGetMaxY(rect); | |
| 141 CGPathMoveToPoint(path, NULL, minX, midY); | |
| 142 CGPathAddArcToPoint(path, NULL, minX, minY, midX, minY, radius); | |
| 143 CGPathAddArcToPoint(path, NULL, maxX, minY, maxX, midY, radius); | |
| 144 CGPathAddArcToPoint(path, NULL, maxX, maxY, midX, maxY, radius); | |
| 145 CGPathAddArcToPoint(path, NULL, minX, maxY, minX, midY, radius); | |
| 146 CGPathCloseSubpath(path); | |
| 147 view.layer.shadowColor = [color CGColor]; | |
| 148 view.layer.shadowOpacity = 1.0; | |
| 149 view.layer.shadowRadius = radius; | |
| 150 view.layer.shadowOffset = CGSizeZero; | |
| 151 view.layer.shadowPath = path; | |
| 152 view.layer.borderWidth = 0.0; | |
| 153 CGPathRelease(path); | |
| 154 } | |
| 155 | |
| 156 UIImage* CaptureView(UIView* view, CGFloat scale) { | |
| 157 UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES /* opaque */, | |
| 158 scale); | |
| 159 CGContext* context = UIGraphicsGetCurrentContext(); | |
| 160 [view.layer renderInContext:context]; | |
| 161 UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); | |
| 162 UIGraphicsEndImageContext(); | |
| 163 return image; | |
| 164 } | |
| 165 | |
| 166 UIImage* GreyImage(UIImage* image) { | |
| 167 DCHECK(image); | |
| 168 // Grey images are always non-retina to improve memory performance. | |
| 169 UIGraphicsBeginImageContextWithOptions(image.size, YES, 1.0); | |
| 170 CGRect greyImageRect = CGRectMake(0, 0, image.size.width, image.size.height); | |
| 171 [image drawInRect:greyImageRect blendMode:kCGBlendModeLuminosity alpha:1.0]; | |
| 172 UIImage* greyImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| 173 UIGraphicsEndImageContext(); | |
| 174 return greyImage; | |
| 175 } | |
| 176 | |
| 177 UIColor* GetPrimaryActionButtonColor() { | |
| 178 return UIColorFromRGB(0x2d6ada, 1.0); | |
| 179 } | |
| 180 | |
| 181 UIColor* GetSettingsBackgroundColor() { | |
| 182 CGFloat rgb = 237.0 / 255.0; | |
| 183 return [UIColor colorWithWhite:rgb alpha:1]; | |
| 184 } | |
| 185 | |
| 186 UIImage* ResizeImage(UIImage* image, | |
| 187 CGSize targetSize, | |
| 188 BOOL preserveAspectRatio, | |
| 189 BOOL trimToFit) { | |
| 190 CGSize revisedTargetSize; | |
| 191 CGRect projectTo; | |
| 192 | |
| 193 CalculateProjection([image size], targetSize, preserveAspectRatio, trimToFit, | |
| 194 revisedTargetSize, projectTo); | |
| 195 | |
| 196 if (CGRectEqualToRect(projectTo, CGRectZero)) | |
| 197 return nil; | |
| 198 | |
| 199 // Resize photo. Use UIImage drawing methods because they respect | |
| 200 // UIImageOrientation as opposed to CGContextDrawImage(). | |
| 201 UIGraphicsBeginImageContextWithOptions(revisedTargetSize, NO, image.scale); | |
| 202 [image drawInRect:projectTo]; | |
| 203 UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext(); | |
| 204 UIGraphicsEndImageContext(); | |
| 205 return resizedPhoto; | |
| 206 } | |
| 207 | |
| 208 UIImage* DarkenImage(UIImage* image) { | |
| 209 UIColor* tintColor = [UIColor colorWithWhite:0.22 alpha:0.6]; | |
| 210 return BlurImage(image, | |
| 211 3.0, // blurRadius, | |
| 212 tintColor, | |
| 213 1.8, // saturationDeltaFactor | |
| 214 nil); | |
| 215 } | |
| 216 | |
| 217 UIImage* BlurImage(UIImage* image, | |
| 218 CGFloat blurRadius, | |
| 219 UIColor* tintColor, | |
| 220 CGFloat saturationDeltaFactor, | |
| 221 UIImage* maskImage) { | |
| 222 // This code is heavily inspired by the UIImageEffect sample project, | |
| 223 // presented at WWDC and available from Apple. | |
| 224 DCHECK(image.size.width >= 1 && image.size.height >= 1); | |
| 225 DCHECK(image.CGImage); | |
| 226 DCHECK(!maskImage || maskImage.CGImage); | |
| 227 | |
| 228 CGRect imageRect = {CGPointZero, image.size}; | |
| 229 UIImage* effectImage = nil; | |
| 230 | |
| 231 BOOL hasBlur = blurRadius > __FLT_EPSILON__; | |
| 232 BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; | |
| 233 if (hasBlur || hasSaturationChange) { | |
| 234 UIGraphicsBeginImageContextWithOptions(image.size, | |
| 235 NO, // opaque. | |
| 236 [[UIScreen mainScreen] scale]); | |
| 237 CGContextRef effectInContext = UIGraphicsGetCurrentContext(); | |
| 238 CGContextScaleCTM(effectInContext, 1.0, -1.0); | |
| 239 CGContextTranslateCTM(effectInContext, 0, -image.size.height); | |
| 240 CGContextDrawImage(effectInContext, imageRect, image.CGImage); | |
| 241 | |
| 242 vImage_Buffer effectInBuffer; | |
| 243 effectInBuffer.data = CGBitmapContextGetData(effectInContext); | |
| 244 effectInBuffer.width = CGBitmapContextGetWidth(effectInContext); | |
| 245 effectInBuffer.height = CGBitmapContextGetHeight(effectInContext); | |
| 246 effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext); | |
| 247 | |
| 248 UIGraphicsBeginImageContextWithOptions(image.size, | |
| 249 NO, // opaque. | |
| 250 [[UIScreen mainScreen] scale]); | |
| 251 CGContextRef effectOutContext = UIGraphicsGetCurrentContext(); | |
| 252 vImage_Buffer effectOutBuffer; | |
| 253 effectOutBuffer.data = CGBitmapContextGetData(effectOutContext); | |
| 254 effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext); | |
| 255 effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext); | |
| 256 effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext); | |
| 257 | |
| 258 // Those are swapped as effects are applied. | |
| 259 vImage_Buffer* inBuffer = &effectInBuffer; | |
| 260 vImage_Buffer* outBuffer = &effectOutBuffer; | |
| 261 | |
| 262 if (hasBlur) { | |
| 263 // A description of how to compute the box kernel width from the Gaussian | |
| 264 // radius (aka standard deviation) appears in the SVG spec: | |
| 265 // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement | |
| 266 // | |
| 267 // For larger values of 's' (s >= 2.0), an approximation can be used: | |
| 268 // Three successive box-blurs build a piece-wise quadratic convolution | |
| 269 // kernel, which approximates the Gaussian kernel to within roughly 3%. | |
| 270 // | |
| 271 // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) | |
| 272 // | |
| 273 // ... if d is odd, use three box-blurs of size 'd', centered on the | |
| 274 // output pixel. | |
| 275 // | |
| 276 CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale]; | |
| 277 NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); | |
| 278 if (radius % 2 != 1) { | |
| 279 // force radius to be odd so that the three box-blur methodology works. | |
| 280 radius += 1; | |
| 281 } | |
| 282 for (int i = 0; i < 3; ++i) { | |
| 283 vImageBoxConvolve_ARGB8888(inBuffer, // src. | |
| 284 outBuffer, // dst. | |
| 285 NULL, // tempBuffer. | |
| 286 0, // srcOffsetToROI_X. | |
| 287 0, // srcOffsetToROI_Y | |
| 288 radius, // kernel_height | |
| 289 radius, // kernel_width | |
| 290 0, // backgroundColor | |
| 291 kvImageEdgeExtend); // flags | |
| 292 vImage_Buffer* temp = inBuffer; | |
| 293 inBuffer = outBuffer; | |
| 294 outBuffer = temp; | |
| 295 } | |
| 296 } | |
| 297 if (hasSaturationChange) { | |
| 298 CGFloat s = saturationDeltaFactor; | |
| 299 CGFloat floatingPointSaturationMatrix[] = { | |
| 300 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, | |
| 301 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, | |
| 302 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, | |
| 303 0, 0, 0, 1 }; | |
| 304 const int32_t divisor = 256; | |
| 305 NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix) / | |
| 306 sizeof(floatingPointSaturationMatrix[0]); | |
| 307 int16_t saturationMatrix[matrixSize]; | |
| 308 for (NSUInteger i = 0; i < matrixSize; ++i) { | |
| 309 saturationMatrix[i] = | |
| 310 (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); | |
| 311 } | |
| 312 vImageMatrixMultiply_ARGB8888(inBuffer, outBuffer, saturationMatrix, | |
| 313 divisor, NULL, NULL, kvImageNoFlags); | |
| 314 } | |
| 315 if (outBuffer == &effectOutBuffer) | |
| 316 effectImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| 317 UIGraphicsEndImageContext(); | |
| 318 | |
| 319 if (!effectImage) | |
| 320 effectImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| 321 UIGraphicsEndImageContext(); | |
| 322 } | |
| 323 | |
| 324 // Set up output context. | |
| 325 UIGraphicsBeginImageContextWithOptions(image.size, | |
| 326 NO, // opaque | |
| 327 [[UIScreen mainScreen] scale]); | |
| 328 CGContextRef outputContext = UIGraphicsGetCurrentContext(); | |
| 329 CGContextScaleCTM(outputContext, 1.0, -1.0); | |
| 330 CGContextTranslateCTM(outputContext, 0, -image.size.height); | |
| 331 | |
| 332 // Draw base image. | |
| 333 CGContextDrawImage(outputContext, imageRect, image.CGImage); | |
| 334 | |
| 335 // Draw effect image. | |
| 336 if (effectImage) { | |
| 337 gfx::ScopedCGContextSaveGState context(outputContext); | |
| 338 if (maskImage) | |
| 339 CGContextClipToMask(outputContext, imageRect, maskImage.CGImage); | |
| 340 CGContextDrawImage(outputContext, imageRect, effectImage.CGImage); | |
| 341 } | |
| 342 | |
| 343 // Add in color tint. | |
| 344 if (tintColor) { | |
| 345 gfx::ScopedCGContextSaveGState context(outputContext); | |
| 346 CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); | |
| 347 CGContextFillRect(outputContext, imageRect); | |
| 348 } | |
| 349 | |
| 350 // Output image is ready. | |
| 351 UIImage* outputImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| 352 UIGraphicsEndImageContext(); | |
| 353 | |
| 354 return outputImage; | |
| 355 } | |
| 356 | |
| 357 UIInterfaceOrientation GetInterfaceOrientation() { | |
| 358 return [[UIApplication sharedApplication] statusBarOrientation]; | |
| 359 } | |
| 360 | |
| 361 CGFloat CurrentKeyboardHeight(NSValue* keyboardFrameValue) { | |
| 362 CGSize keyboardSize = [keyboardFrameValue CGRectValue].size; | |
| 363 if (base::ios::IsRunningOnIOS8OrLater()) { | |
| 364 return keyboardSize.height; | |
| 365 } else { | |
| 366 return IsPortrait() ? keyboardSize.height : keyboardSize.width; | |
| 367 } | |
| 368 } | |
| 369 | |
| 370 UIImage* ImageWithColor(UIColor* color) { | |
| 371 CGRect rect = CGRectMake(0, 0, 1, 1); | |
| 372 UIGraphicsBeginImageContext(rect.size); | |
| 373 CGContextRef context = UIGraphicsGetCurrentContext(); | |
| 374 CGContextSetFillColorWithColor(context, [color CGColor]); | |
| 375 CGContextFillRect(context, rect); | |
| 376 UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); | |
| 377 UIGraphicsEndImageContext(); | |
| 378 return image; | |
| 379 } | |
| 380 | |
| 381 UIImage* CircularImageFromImage(UIImage* image, CGFloat width) { | |
| 382 CGRect frame = | |
| 383 CGRectMakeAlignedAndCenteredAt(width / 2.0, width / 2.0, width); | |
| 384 | |
| 385 UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0); | |
| 386 CGContextRef context = UIGraphicsGetCurrentContext(); | |
| 387 | |
| 388 CGContextBeginPath(context); | |
| 389 CGContextAddEllipseInRect(context, frame); | |
| 390 CGContextClosePath(context); | |
| 391 CGContextClip(context); | |
| 392 | |
| 393 CGFloat scaleX = frame.size.width / image.size.width; | |
| 394 CGFloat scaleY = frame.size.height / image.size.height; | |
| 395 CGFloat scale = std::max(scaleX, scaleY); | |
| 396 CGContextScaleCTM(context, scale, scale); | |
| 397 | |
| 398 [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; | |
| 399 | |
| 400 image = UIGraphicsGetImageFromCurrentImageContext(); | |
| 401 UIGraphicsEndImageContext(); | |
| 402 | |
| 403 return image; | |
| 404 } | |
| 405 | |
| 406 UIColor* InterpolateFromColorToColor(UIColor* firstColor, | |
| 407 UIColor* secondColor, | |
| 408 CGFloat fraction) { | |
| 409 DCHECK_LE(0.0, fraction); | |
| 410 DCHECK_LE(fraction, 1.0); | |
| 411 CGFloat r1, r2, g1, g2, b1, b2, a1, a2; | |
| 412 GetRGBA(firstColor, &r1, &g1, &b1, &a1); | |
| 413 GetRGBA(secondColor, &r2, &g2, &b2, &a2); | |
| 414 return [UIColor colorWithRed:Lerp(r1, r2, fraction) | |
| 415 green:Lerp(g1, g2, fraction) | |
| 416 blue:Lerp(b1, b2, fraction) | |
| 417 alpha:Lerp(a1, a2, fraction)]; | |
| 418 } | |
| 419 | |
| 420 void ApplyVisualConstraints(NSArray* constraints, | |
| 421 NSDictionary* subviewsDictionary, | |
| 422 UIView* parentView) { | |
| 423 for (NSString* constraint in constraints) { | |
| 424 DCHECK([constraint isKindOfClass:[NSString class]]); | |
| 425 [parentView | |
| 426 addConstraints:[NSLayoutConstraint | |
| 427 constraintsWithVisualFormat:constraint | |
| 428 options:0 | |
| 429 metrics:nil | |
| 430 views:subviewsDictionary]]; | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 void AddSameCenterXConstraint(UIView* parentView, UIView* subview) { | |
| 435 DCHECK_EQ(parentView, [subview superview]); | |
| 436 [parentView addConstraint:[NSLayoutConstraint | |
| 437 constraintWithItem:subview | |
| 438 attribute:NSLayoutAttributeCenterX | |
| 439 relatedBy:NSLayoutRelationEqual | |
| 440 toItem:parentView | |
| 441 attribute:NSLayoutAttributeCenterX | |
| 442 multiplier:1 | |
| 443 constant:0]]; | |
| 444 } | |
| 445 | |
| 446 void AddSameCenterYConstraint(UIView* parentView, | |
| 447 UIView* subview1, | |
| 448 UIView* subview2) { | |
| 449 DCHECK_EQ(parentView, [subview1 superview]); | |
| 450 DCHECK_EQ(parentView, [subview2 superview]); | |
| 451 DCHECK_NE(subview1, subview2); | |
| 452 [parentView addConstraint:[NSLayoutConstraint | |
| 453 constraintWithItem:subview1 | |
| 454 attribute:NSLayoutAttributeCenterY | |
| 455 relatedBy:NSLayoutRelationEqual | |
| 456 toItem:subview2 | |
| 457 attribute:NSLayoutAttributeCenterY | |
| 458 multiplier:1 | |
| 459 constant:0]]; | |
| 460 } | |
| OLD | NEW |