Perf improvements for CVC rendering

Fixes some low-hanging expensive renders in CVC. Namely:
- There are a few places where shadow paths can be trivially calculated.
  Explicitly setting the shadow path saves an offscreen pass.
- Adopts CALayer corner masking where feasible (e.g. layers with
  consistent corner radii).
This commit is contained in:
Michelle Linington 2021-04-08 00:41:27 -07:00
parent a0d542657b
commit 01a4ee4cc7
10 changed files with 168 additions and 141 deletions

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -120,6 +120,9 @@ class CallButton: UIButton {
currentConstraints.removeAll() currentConstraints.removeAll()
currentConstraints += circleView.autoSetDimensions(to: CGSize(square: currentIconSize)) currentConstraints += circleView.autoSetDimensions(to: CGSize(square: currentIconSize))
circleView.layer.shadowPath = UIBezierPath(
ovalIn: CGRect(origin: .zero, size: .square(currentIconSize))
).cgPath
currentConstraints += iconView.autoPinEdgesToSuperviewEdges(with: currentIconInsets) currentConstraints += iconView.autoPinEdgesToSuperviewEdges(with: currentIconInsets)
if let dropdownIconView = dropdownIconView { if let dropdownIconView = dropdownIconView {
currentConstraints.append(dropdownIconView.autoPinEdge(.leading, to: .trailing, of: iconView, withOffset: isSmall ? 0 : 2)) currentConstraints.append(dropdownIconView.autoPinEdge(.leading, to: .trailing, of: iconView, withOffset: isSmall ? 0 : 2))

View File

@ -250,6 +250,9 @@ class ProfileSettingsViewController: OWSTableViewController2 {
cameraImageContainer.layer.shadowOpacity = 0.2 cameraImageContainer.layer.shadowOpacity = 0.2
cameraImageContainer.layer.shadowRadius = 4 cameraImageContainer.layer.shadowRadius = 4
cameraImageContainer.layer.shadowOffset = CGSize(width: 0, height: 2) cameraImageContainer.layer.shadowOffset = CGSize(width: 0, height: 2)
cameraImageContainer.layer.shadowPath = UIBezierPath(
ovalIn: CGRect(origin: .zero, size: .square(32))
).cgPath
cell.contentView.addSubview(cameraImageContainer) cell.contentView.addSubview(cameraImageContainer)
cameraImageContainer.autoPinTrailing(toEdgeOf: avatarImageView) cameraImageContainer.autoPinTrailing(toEdgeOf: avatarImageView)
@ -260,6 +263,9 @@ class ProfileSettingsViewController: OWSTableViewController2 {
secondaryShadowView.layer.shadowOpacity = 0.12 secondaryShadowView.layer.shadowOpacity = 0.12
secondaryShadowView.layer.shadowRadius = 16 secondaryShadowView.layer.shadowRadius = 16
secondaryShadowView.layer.shadowOffset = CGSize(width: 0, height: 4) secondaryShadowView.layer.shadowOffset = CGSize(width: 0, height: 4)
secondaryShadowView.layer.shadowPath = UIBezierPath(
ovalIn: CGRect(origin: .zero, size: .square(32))
).cgPath
cameraImageContainer.addSubview(secondaryShadowView) cameraImageContainer.addSubview(secondaryShadowView)
secondaryShadowView.autoPinEdgesToSuperviewEdges() secondaryShadowView.autoPinEdgesToSuperviewEdges()

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -167,6 +167,42 @@ public class QuotedMessageView: UIView {
: conversationStyle.quotingSelfHighlightColor()) : conversationStyle.quotingSelfHighlightColor())
} }
func createInnerBubbleView(sharpCorners: OWSDirectionalRectCorner) -> UIView {
let sharpCornerRadius: CGFloat = 4
let wideCornerRadius: CGFloat = 12
let innerBubbleView: UIView
if sharpCorners.isEmpty || sharpCorners.contains(.allCorners) {
innerBubbleView = UIView()
innerBubbleView.layer.maskedCorners = .all
innerBubbleView.layer.cornerRadius = sharpCorners.isEmpty ? wideCornerRadius : sharpCornerRadius
} else {
// Slow path. CA isn't optimized to handle corners of multiple radii
// Let's do it by hand with a CAShapeLayer
let maskLayer = CAShapeLayer()
innerBubbleView = OWSLayerView(frame: .zero) { (layerView: UIView) in
let layerFrame = layerView.bounds
let bubbleLeft: CGFloat = 0
let bubbleRight = layerFrame.width
let bubbleTop: CGFloat = 0
let bubbleBottom = layerFrame.height
let bezierPath = OWSBubbleView.roundedBezierRect(withBubbleTop: bubbleTop,
bubbleLeft: bubbleLeft,
bubbleBottom: bubbleBottom,
bubbleRight: bubbleRight,
sharpCornerRadius: sharpCornerRadius,
wideCornerRadius: wideCornerRadius,
sharpCorners: sharpCorners)
maskLayer.path = bezierPath.cgPath
}
innerBubbleView.layer.mask = maskLayer
}
innerBubbleView.backgroundColor = conversationStyle.quotedReplyBubbleColor
return innerBubbleView
}
func createContents(rootView: UIView, func createContents(rootView: UIView,
quotedAuthorLabel: UILabel, quotedAuthorLabel: UILabel,
quotedTextLabel: UILabel, quotedTextLabel: UILabel,
@ -179,32 +215,7 @@ public class QuotedMessageView: UIView {
rootView.layoutMargins = .zero rootView.layoutMargins = .zero
rootView.clipsToBounds = true rootView.clipsToBounds = true
let maskLayer = CAShapeLayer() let innerBubbleView = createInnerBubbleView(sharpCorners: sharpCorners)
let innerBubbleView = OWSLayerView(frame: .zero) { (layerView: UIView) in
let layerFrame = layerView.bounds
let bubbleLeft: CGFloat = 0
let bubbleRight = layerFrame.width
let bubbleTop: CGFloat = 0
let bubbleBottom = layerFrame.height
let sharpCornerRadius: CGFloat = 4
let wideCornerRadius: CGFloat = 12
let bezierPath = OWSBubbleView.roundedBezierRect(withBubbleTop: bubbleTop,
bubbleLeft: bubbleLeft,
bubbleBottom: bubbleBottom,
bubbleRight: bubbleRight,
sharpCornerRadius: sharpCornerRadius,
wideCornerRadius: wideCornerRadius,
sharpCorners: sharpCorners)
maskLayer.path = bezierPath.cgPath
}
innerBubbleView.layer.mask = maskLayer
innerBubbleView.backgroundColor = conversationStyle.quotedReplyBubbleColor
rootView.addSubview(innerBubbleView) rootView.addSubview(innerBubbleView)
innerBubbleView.autoPinLeadingToSuperviewMargin(withInset: bubbleHMargin) innerBubbleView.autoPinLeadingToSuperviewMargin(withInset: bubbleHMargin)
innerBubbleView.autoPinTrailingToSuperviewMargin(withInset: bubbleHMargin) innerBubbleView.autoPinTrailingToSuperviewMargin(withInset: bubbleHMargin)

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
#import "ConversationScrollButton.h" #import "ConversationScrollButton.h"
@ -67,6 +67,7 @@ NS_ASSUME_NONNULL_BEGIN
iconView.userInteractionEnabled = NO; iconView.userInteractionEnabled = NO;
const CGFloat circleSize = self.class.circleSize; const CGFloat circleSize = self.class.circleSize;
CGRect shadowRect = CGRectMake(0, 0, circleSize, circleSize);
UIView *shadowView = [[OWSCircleView alloc] initWithDiameter:circleSize]; UIView *shadowView = [[OWSCircleView alloc] initWithDiameter:circleSize];
self.shadowView = shadowView; self.shadowView = shadowView;
@ -75,6 +76,7 @@ NS_ASSUME_NONNULL_BEGIN
shadowView.layer.shadowRadius = 4; shadowView.layer.shadowRadius = 4;
shadowView.layer.shadowOpacity = 0.05f; shadowView.layer.shadowOpacity = 0.05f;
shadowView.layer.shadowColor = UIColor.blackColor.CGColor; shadowView.layer.shadowColor = UIColor.blackColor.CGColor;
shadowView.layer.shadowPath = [UIBezierPath bezierPathWithOvalInRect:shadowRect].CGPath;
UIView *circleView = [[OWSCircleView alloc] initWithDiameter:circleSize]; UIView *circleView = [[OWSCircleView alloc] initWithDiameter:circleSize];
self.circleView = circleView; self.circleView = circleView;
@ -83,6 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
circleView.layer.shadowRadius = 12.f; circleView.layer.shadowRadius = 12.f;
circleView.layer.shadowOpacity = 0.3f; circleView.layer.shadowOpacity = 0.3f;
circleView.layer.shadowColor = UIColor.blackColor.CGColor; circleView.layer.shadowColor = UIColor.blackColor.CGColor;
circleView.layer.shadowPath = [UIBezierPath bezierPathWithOvalInRect:shadowRect].CGPath;
UIView *unreadBadge = [UIView new]; UIView *unreadBadge = [UIView new];
self.unreadBadge = unreadBadge; self.unreadBadge = unreadBadge;

View File

@ -339,13 +339,9 @@ public protocol LinkPreviewViewDraftDelegate {
// MARK: - // MARK: -
@objc private class LinkPreviewImageView: UIImageView {
public class LinkPreviewImageView: UIImageView { fileprivate enum Rounding: UInt {
private let maskLayer = CAShapeLayer() case standard
@objc
public enum Rounding: UInt {
case none
case asymmetrical case asymmetrical
case circular case circular
} }
@ -353,34 +349,38 @@ public class LinkPreviewImageView: UIImageView {
private let rounding: Rounding private let rounding: Rounding
fileprivate var isHero = false fileprivate var isHero = false
@objc // We only need to use a more complicated corner mask if we're
public init(rounding: Rounding) { // drawing asymmetric corners. This is an exceptional case to match
self.rounding = rounding // the input toolbar curve.
private let asymmetricCornerMask = CAShapeLayer()
init(rounding: Rounding) {
self.rounding = rounding
super.init(frame: .zero) super.init(frame: .zero)
self.layer.mask = maskLayer if rounding == .asymmetrical {
layer.mask = asymmetricCornerMask
}
} }
public required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
self.rounding = .none self.rounding = .standard
super.init(coder: aDecoder) super.init(coder: aDecoder)
} }
public override var bounds: CGRect { override var bounds: CGRect {
didSet { didSet {
updateMaskLayer() updateMaskLayer()
} }
} }
public override var frame: CGRect { override var frame: CGRect {
didSet { didSet {
updateMaskLayer() updateMaskLayer()
} }
} }
public override var center: CGPoint { override var center: CGPoint {
didSet { didSet {
updateMaskLayer() updateMaskLayer()
} }
@ -388,62 +388,62 @@ public class LinkPreviewImageView: UIImageView {
private func updateMaskLayer() { private func updateMaskLayer() {
let layerBounds = self.bounds let layerBounds = self.bounds
guard rounding != .circular else {
maskLayer.path = UIBezierPath(ovalIn: layerBounds).cgPath
return
}
// One of the corners has assymetrical rounding to match the input toolbar border.
// This is somewhat inconvenient.
let upperLeft = CGPoint(x: 0, y: 0)
let upperRight = CGPoint(x: layerBounds.size.width, y: 0)
let lowerRight = CGPoint(x: layerBounds.size.width, y: layerBounds.size.height)
let lowerLeft = CGPoint(x: 0, y: layerBounds.size.height)
let bigRounding: CGFloat = 14 let bigRounding: CGFloat = 14
let smallRounding: CGFloat = 6 let smallRounding: CGFloat = 6
let upperLeftRounding: CGFloat switch rounding {
let upperRightRounding: CGFloat case .standard:
if rounding == .asymmetrical { layer.cornerRadius = smallRounding
upperLeftRounding = CurrentAppContext().isRTL ? smallRounding : bigRounding layer.maskedCorners = isHero ? .top : .all
upperRightRounding = CurrentAppContext().isRTL ? bigRounding : smallRounding case .circular:
} else { layer.cornerRadius = bounds.size.smallerAxis
upperLeftRounding = smallRounding layer.maskedCorners = .all
upperRightRounding = smallRounding case .asymmetrical:
// This uses a more expensive layer mask to clip corners
// with different radii.
// This should only be used in the input toolbar so perf is
// less of a concern here.
owsAssertDebug(!isHero, "Link preview drafts never use hero images")
let upperLeft = CGPoint(x: 0, y: 0)
let upperRight = CGPoint(x: layerBounds.size.width, y: 0)
let lowerRight = CGPoint(x: layerBounds.size.width, y: layerBounds.size.height)
let lowerLeft = CGPoint(x: 0, y: layerBounds.size.height)
let upperLeftRounding: CGFloat = CurrentAppContext().isRTL ? smallRounding : bigRounding
let upperRightRounding: CGFloat = CurrentAppContext().isRTL ? bigRounding : smallRounding
let lowerRightRounding = smallRounding
let lowerLeftRounding = smallRounding
let path = UIBezierPath()
// It's sufficient to "draw" the rounded corners and not the edges that connect them.
path.addArc(withCenter: upperLeft.offsetBy(dx: +upperLeftRounding).offsetBy(dy: +upperLeftRounding),
radius: upperLeftRounding,
startAngle: CGFloat.pi * 1.0,
endAngle: CGFloat.pi * 1.5,
clockwise: true)
path.addArc(withCenter: upperRight.offsetBy(dx: -upperRightRounding).offsetBy(dy: +upperRightRounding),
radius: upperRightRounding,
startAngle: CGFloat.pi * 1.5,
endAngle: CGFloat.pi * 0.0,
clockwise: true)
path.addArc(withCenter: lowerRight.offsetBy(dx: -lowerRightRounding).offsetBy(dy: -lowerRightRounding),
radius: lowerRightRounding,
startAngle: CGFloat.pi * 0.0,
endAngle: CGFloat.pi * 0.5,
clockwise: true)
path.addArc(withCenter: lowerLeft.offsetBy(dx: +lowerLeftRounding).offsetBy(dy: -lowerLeftRounding),
radius: lowerLeftRounding,
startAngle: CGFloat.pi * 0.5,
endAngle: CGFloat.pi * 1.0,
clockwise: true)
asymmetricCornerMask.path = path.cgPath
} }
let lowerRightRounding = isHero ? 0 : smallRounding
let lowerLeftRounding = isHero ? 0 : smallRounding
let path = UIBezierPath()
// It's sufficient to "draw" the rounded corners and not the edges that connect them.
path.addArc(withCenter: upperLeft.offsetBy(dx: +upperLeftRounding).offsetBy(dy: +upperLeftRounding),
radius: upperLeftRounding,
startAngle: CGFloat.pi * 1.0,
endAngle: CGFloat.pi * 1.5,
clockwise: true)
path.addArc(withCenter: upperRight.offsetBy(dx: -upperRightRounding).offsetBy(dy: +upperRightRounding),
radius: upperRightRounding,
startAngle: CGFloat.pi * 1.5,
endAngle: CGFloat.pi * 0.0,
clockwise: true)
path.addArc(withCenter: lowerRight.offsetBy(dx: -lowerRightRounding).offsetBy(dy: -lowerRightRounding),
radius: lowerRightRounding,
startAngle: CGFloat.pi * 0.0,
endAngle: CGFloat.pi * 0.5,
clockwise: true)
path.addArc(withCenter: lowerLeft.offsetBy(dx: +lowerLeftRounding).offsetBy(dy: -lowerLeftRounding),
radius: lowerLeftRounding,
startAngle: CGFloat.pi * 0.5,
endAngle: CGFloat.pi * 1.0,
clockwise: true)
maskLayer.path = path.cgPath
} }
} }
@ -472,6 +472,7 @@ public class LinkPreviewView: UIStackView {
public var hasAsymmetricalRounding: Bool = false { public var hasAsymmetricalRounding: Bool = false {
didSet { didSet {
AssertIsOnMainThread() AssertIsOnMainThread()
owsAssertDebug(isDraft)
if hasAsymmetricalRounding != oldValue { if hasAsymmetricalRounding != oldValue {
updateContents() updateContents()
@ -1016,13 +1017,7 @@ public class LinkPreviewView: UIStackView {
owsFailDebug("Could not load image.") owsFailDebug("Could not load image.")
return nil return nil
} }
let rounding: LinkPreviewImageView.Rounding = { let imageView = LinkPreviewImageView(rounding: roundingParam ?? .standard)
if let roundingParam = roundingParam {
return roundingParam
}
return self.hasAsymmetricalRounding ? .asymmetrical : .none
}()
let imageView = LinkPreviewImageView(rounding: rounding)
imageView.image = image imageView.image = image
imageView.isHero = Self.sentIsHero(state: state) imageView.isHero = Self.sentIsHero(state: state)
return imageView return imageView
@ -1041,7 +1036,7 @@ public class LinkPreviewView: UIStackView {
owsFailDebug("Could not load image.") owsFailDebug("Could not load image.")
return nil return nil
} }
let rounding: LinkPreviewImageView.Rounding = hasAsymmetricalRounding ? .asymmetrical : .none let rounding: LinkPreviewImageView.Rounding = hasAsymmetricalRounding ? .asymmetrical : .standard
let imageView = LinkPreviewImageView(rounding: rounding) let imageView = LinkPreviewImageView(rounding: rounding)
imageView.image = image imageView.image = image
return imageView return imageView
@ -1268,29 +1263,6 @@ public class LinkPreviewView: UIStackView {
return CGSize(width: width, height: height) return CGSize(width: width, height: height)
} }
@objc
public func addBorderViews(bubbleView: OWSBubbleView) {
if let heroImageView = self.heroImageView {
let borderView = OWSBubbleShapeView(draw: ())
borderView.strokeColor = Theme.primaryTextColor
borderView.strokeThickness = CGHairlineWidthFraction(1.8)
heroImageView.addSubview(borderView)
bubbleView.addPartnerView(borderView)
borderView.autoPinEdgesToSuperviewEdges()
}
if let sentBodyView = self.sentBodyView {
let borderView = OWSBubbleShapeView(draw: ())
let borderColor = (Theme.isDarkThemeEnabled ? UIColor.ows_gray60 : UIColor.ows_gray15)
borderView.strokeColor = borderColor
borderView.strokeThickness = CGHairlineWidthFraction(1.8)
sentBodyView.addSubview(borderView)
bubbleView.addPartnerView(borderView)
borderView.autoPinEdgesToSuperviewEdges()
} else {
owsFailDebug("Missing sentBodyView")
}
}
@objc func didTapCancel(sender: UIButton) { @objc func didTapCancel(sender: UIButton) {
self.draftDelegate?.linkPreviewDidCancel() self.draftDelegate?.linkPreviewDidCancel()
} }

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
#import "OWSBubbleShapeView.h" #import "OWSBubbleShapeView.h"
@ -255,9 +255,6 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
self.layer.mask = self.maskLayer; self.layer.mask = self.maskLayer;
break; break;
case OWSBubbleShapeViewMode_InnerShadow: { case OWSBubbleShapeViewMode_InnerShadow: {
self.maskLayer.path = bezierPath.CGPath;
self.layer.mask = self.maskLayer;
// Inner shadow. // Inner shadow.
// This should usually not be visible; it is used to distinguish // This should usually not be visible; it is used to distinguish
// profile pics from the background if they are similar. // profile pics from the background if they are similar.
@ -267,16 +264,21 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
UIBezierPath *shadowPath = [bezierPath copy]; UIBezierPath *shadowPath = [bezierPath copy];
// This can be any value large enough to cast a sufficiently large shadow. // This can be any value large enough to cast a sufficiently large shadow.
CGFloat shadowInset = -(self.innerShadowRadius * 4.f); CGFloat shadowInset = -(self.innerShadowRadius * 4.f);
[shadowPath
appendPath:[UIBezierPath bezierPathWithRect:CGRectInset(shadowBounds, shadowInset, shadowInset)]]; CGRect outerRect = CGRectInset(shadowBounds, shadowInset, shadowInset);
UIBezierPath *outerPath = [UIBezierPath bezierPathWithRect:outerRect];
// -[CALayer shadowPath] uses the non-zero winding rule
// Reverse the path to make the directions line up correctly.
[shadowPath appendPath:[outerPath bezierPathByReversingPath]];
// This can be any color since the fill should be clipped. // This can be any color since the fill should be clipped.
self.shapeLayer.fillColor = UIColor.blackColor.CGColor; self.shapeLayer.fillColor = UIColor.blackColor.CGColor;
self.shapeLayer.path = shadowPath.CGPath; self.shapeLayer.path = shadowPath.CGPath;
self.shapeLayer.fillRule = kCAFillRuleEvenOdd;
self.shapeLayer.shadowColor = self.innerShadowColor.CGColor; self.shapeLayer.shadowColor = self.innerShadowColor.CGColor;
self.shapeLayer.shadowRadius = self.innerShadowRadius; self.shapeLayer.shadowRadius = self.innerShadowRadius;
self.shapeLayer.shadowOpacity = self.innerShadowOpacity; self.shapeLayer.shadowOpacity = self.innerShadowOpacity;
self.shapeLayer.shadowOffset = CGSizeZero; self.shapeLayer.shadowOffset = CGSizeZero;
self.shapeLayer.shadowPath = shadowPath.CGPath;
break; break;
} }

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -12,7 +12,9 @@ typedef NS_OPTIONS(NSUInteger, OWSDirectionalRectCorner) {
OWSDirectionalRectCornerTopTrailing = 1 << 1, OWSDirectionalRectCornerTopTrailing = 1 << 1,
OWSDirectionalRectCornerBottomLeading = 1 << 2, OWSDirectionalRectCornerBottomLeading = 1 << 2,
OWSDirectionalRectCornerBottomTrailing = 1 << 3, OWSDirectionalRectCornerBottomTrailing = 1 << 3,
OWSDirectionalRectCornerAllCorners = ~0UL
OWSDirectionalRectCornerAllCorners = OWSDirectionalRectCornerTopLeading | OWSDirectionalRectCornerTopTrailing
| OWSDirectionalRectCornerBottomLeading | OWSDirectionalRectCornerBottomTrailing
}; };
@class OWSBubbleView; @class OWSBubbleView;

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
#import "OWSBubbleView.h" #import "OWSBubbleView.h"
@ -73,8 +73,8 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
[self.layer addSublayer:self.gradientLayer]; [self.layer addSublayer:self.gradientLayer];
self.gradientLayer.hidden = YES; self.gradientLayer.hidden = YES;
self.layer.masksToBounds = YES;
self.maskLayer = [CAShapeLayer new]; self.maskLayer = [CAShapeLayer new];
self.layer.mask = self.maskLayer;
_partnerViews = [NSMutableArray new]; _partnerViews = [NSMutableArray new];
@ -211,6 +211,24 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
self.maskLayer.path = bezierPath.CGPath; self.maskLayer.path = bezierPath.CGPath;
BOOL noSharpCorners = (self.sharpCorners == 0);
BOOL allSharpCorners
= (self.sharpCorners & OWSDirectionalRectCornerAllCorners) == OWSDirectionalRectCornerAllCorners;
if (noSharpCorners || allSharpCorners) {
// Although we have a bubble bezier path, it's just a simple rounded rect
// It's more performant to use the CA corner mask property than a layer mask
CGFloat sharpRadius = kOWSMessageCellCornerRadius_Small;
CGFloat wideRadius = kOWSMessageCellCornerRadius_Large;
self.layer.cornerRadius = (self.sharpCorners == 0) ? wideRadius : sharpRadius;
self.layer.mask = nil;
} else {
self.layer.cornerRadius = 0;
self.layer.mask = self.maskLayer;
}
[CATransaction commit]; [CATransaction commit];
} }

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -117,6 +117,7 @@ open class TooltipView: UIView {
bezierPath.addLine(to: tailPoint) bezierPath.addLine(to: tailPoint)
shapeLayer.path = bezierPath.cgPath shapeLayer.path = bezierPath.cgPath
shapeLayer.shadowPath = bezierPath.cgPath
shapeLayer.frame = view.bounds shapeLayer.frame = view.bounds
} }

View File

@ -621,6 +621,15 @@ public extension UIBezierPath {
} }
} }
public extension CACornerMask {
static let top: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
static let bottom: CACornerMask = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
static let left: CACornerMask = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
static let right: CACornerMask = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
static let all: CACornerMask = top.union(bottom)
}
// MARK: - // MARK: -
@objc @objc