Nicer toolbar buttons

This commit is contained in:
James Magahern
2020-08-14 15:55:08 -07:00
parent 9b36a3ace5
commit 69029a3195
12 changed files with 534 additions and 187 deletions

View File

@@ -0,0 +1,72 @@
//
// GradientView.swift
// App
//
// Created by James Magahern on 8/14/20.
//
import UIKit
class GradientView: UIImageView
{
enum Direction {
case horizontal
case vertical
}
var direction: Direction = .horizontal {
didSet { image = nil; setNeedsLayout() }
}
var colors: [UIColor] = [] {
didSet { image = nil; setNeedsLayout() }
}
private var generatedImageSize: CGSize?
convenience init(direction: Direction, colors: [UIColor]) {
self.init(image: nil)
self.direction = direction
self.colors = colors
}
private func gradientImage(forSize size: CGSize) -> UIImage? {
var image: UIImage? = nil
UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() {
let gradientColorsArray = self.colors.map { $0.cgColor }
if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: nil) {
if direction == .horizontal {
context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
} else if direction == .vertical {
context.drawLinearGradient(gradient, start: CGPoint(x: size.width / 2, y: 0), end: CGPoint(x: size.width / 2, y: size.height), options: CGGradientDrawingOptions())
}
}
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
}
return image
}
override func layoutSubviews() {
super.layoutSubviews()
var needsNewImage: Bool = image == nil
if let generatedImageSize = generatedImageSize {
if generatedImageSize != bounds.size {
needsNewImage = true
}
} else {
needsNewImage = true
}
if needsNewImage {
image = gradientImage(forSize: bounds.size)
generatedImageSize = bounds.size
}
}
}

View File

@@ -0,0 +1,130 @@
//
// ReliefButton.swift
// App
//
// Created by James Magahern on 8/14/20.
//
import UIKit
class ReliefButton: UIButton
{
internal let shadowView = UIView(frame: .zero)
internal let backgroundView = GradientView(direction: .vertical, colors: ReliefButton.gradientColors(inverted: false, darkMode: false))
static let padding = CGFloat(24.0)
override var isHighlighted: Bool {
didSet {
setBackgroundInverted(isHighlighted)
}
}
init() {
super.init(frame: .zero)
let cornerRadius = CGFloat(4.0)
self.tintColor = .init(dynamicProvider: { traitCollection -> UIColor in
if traitCollection.userInterfaceStyle == .light {
return .init(white: 0.15, alpha: 1.0)
} else {
return .white
}
})
shadowView.alpha = 0.28
shadowView.backgroundColor = UIColor(white: 0.0, alpha: 1.0)
shadowView.isUserInteractionEnabled = false
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
shadowView.layer.shadowRadius = 1.0
shadowView.layer.shadowOpacity = 1.0
shadowView.layer.cornerRadius = cornerRadius
shadowView.layer.masksToBounds = false
shadowView.layer.shouldRasterize = true
shadowView.layer.rasterizationScale = UIScreen.main.scale
addSubview(shadowView)
backgroundView.layer.cornerRadius = cornerRadius
backgroundView.isUserInteractionEnabled = false
backgroundView.layer.masksToBounds = true
backgroundView.layer.borderWidth = 1.0
addSubview(backgroundView)
traitCollectionDidChange(nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
static func gradientColors(inverted: Bool, darkMode: Bool) -> [UIColor] {
if darkMode {
if !inverted {
return [
UIColor(white: 0.30, alpha: 1.0),
UIColor(white: 0.10, alpha: 1.0)
]
} else {
return [
UIColor(white: 0.10, alpha: 1.0),
UIColor(white: 0.30, alpha: 1.0)
]
}
} else {
if !inverted {
return [
UIColor(white: 0.98, alpha: 1.0),
UIColor(white: 0.93, alpha: 1.0)
]
} else {
return [
UIColor(white: 0.88, alpha: 1.0),
UIColor(white: 0.93, alpha: 1.0)
]
}
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setBackgroundInverted(isHighlighted)
backgroundView.layer.borderColor = { traitCollection -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .init(white: 0.3, alpha: 1.0)
} else {
return .white
}
}(traitCollection).cgColor
}
internal func setBackgroundInverted(_ inverted: Bool) {
let darkMode: Bool = (traitCollection.userInterfaceStyle == .dark)
backgroundView.colors = Self.gradientColors(inverted: inverted, darkMode: darkMode)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let inset = CGFloat(7.0)
return contentRect.insetBy(dx: inset, dy: inset)
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let ourSize = super.sizeThatFits(size)
return CGSize(width: ourSize.width + Self.padding, height: ourSize.height)
}
override func layoutSubviews() {
self.imageView?.contentMode = .scaleAspectFit
super.layoutSubviews()
sendSubviewToBack(backgroundView)
sendSubviewToBack(shadowView)
let backgroundDimension = bounds.height - 1.0
backgroundView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundDimension, height: backgroundDimension))
backgroundView.frame = backgroundView.frame.centeredX(inRect: bounds)
shadowView.frame = backgroundView.frame
}
}

View File

@@ -0,0 +1,57 @@
//
// SegmentedReliefButton.swift
// App
//
// Created by James Magahern on 8/14/20.
//
import UIKit
class SegmentedReliefButton: ReliefButton
{
var children: [ReliefButton] = [] {
willSet { children.forEach { $0.removeFromSuperview() } }
didSet { children.forEach { addSubview($0) }; setNeedsLayout() }
}
init(children: [ReliefButton]) {
super.init()
self.children = children
}
override var isHighlighted: Bool {
didSet {}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let width: CGFloat = children.reduce(0.0) { (result, button) -> CGFloat in
return result + button.sizeThatFits(size).width
}
return CGSize(width: width, height: size.height)
}
override func layoutSubviews() {
super.layoutSubviews()
backgroundView.frame = bounds
shadowView.frame = bounds
var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height))
for child in children {
child.shadowView.isHidden = true
child.backgroundView.layer.borderWidth = 0
bringSubviewToFront(child)
let childSize = child.sizeThatFits(bounds.size)
buttonRect.size = CGSize(width: childSize.width, height: bounds.height)
child.frame = buttonRect
buttonRect.origin.x += buttonRect.width
}
}
}

View File

@@ -24,8 +24,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window.makeKeyAndVisible()
self.window = window
windowScene.titlebar?.titleVisibility = .hidden
windowScene.titlebar?.separatorStyle = .none
#if targetEnvironment(macCatalyst)
windowScene.titlebar?.titleVisibility = .hidden
windowScene.titlebar?.separatorStyle = .none
#endif
}
}

View File

@@ -7,7 +7,7 @@
import UIKit
class ScriptControllerIconView: UIButton
class ScriptControllerIconView: ReliefButton
{
public var shieldsDown: Bool = false {
didSet { setNeedsLayout() }
@@ -23,8 +23,9 @@ class ScriptControllerIconView: UIButton
private let shieldsUpImage = UIImage(systemName: "shield")
private let shieldsPartiallyUpImage = UIImage(systemName: "shield.lefthalf.fill")
convenience init() {
self.init(frame: .zero)
override init() {
super.init()
addSubview(labelView)
@@ -39,6 +40,10 @@ class ScriptControllerIconView: UIButton
setBlockedScriptsNumber(0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func setBlockedScriptsNumber(_ num: Int) {
if num > 0 {
labelView.isHidden = false

View File

@@ -10,11 +10,14 @@ import UIKit
class TitlebarView: UIView
{
private let titleLabelView = UILabel(frame: .zero)
private let backgroundImageView = UIImageView(frame: .zero)
private let backgroundView = GradientView(direction: .horizontal, colors: [
UIColor(red: 0.101, green: 0.176, blue: 0.415, alpha: 1.0),
UIColor(red: 0.153, green: 0.000, blue: 0.153, alpha: 1.0)
])
convenience init() {
self.init(frame: .zero)
addSubview(backgroundImageView)
addSubview(backgroundView)
addSubview(titleLabelView)
titleLabelView.textColor = .white
@@ -23,7 +26,7 @@ class TitlebarView: UIView
titleLabelView.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
titleLabelView.font = UIFont.boldSystemFont(ofSize: 12.0)
backgroundImageView.alpha = 0.98
backgroundView.alpha = 0.98
}
func setTitle(_ title: String) {
@@ -49,37 +52,10 @@ class TitlebarView: UIView
}
}
private func backgroundImageForSize(_ size: CGSize) -> UIImage? {
var image: UIImage? = nil
UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0))
if let context = UIGraphicsGetCurrentContext() {
let gradientColorsArray = [
UIColor(red: 0.101, green: 0.176, blue: 0.415, alpha: 1.0).cgColor,
UIColor(red: 0.153, green: 0.000, blue: 0.153, alpha: 1.0).cgColor
]
if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: nil) {
context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
}
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
}
return image
}
override func layoutSubviews() {
super.layoutSubviews()
backgroundImageView.frame = bounds
backgroundView.frame = bounds
titleLabelView.frame = bounds.avoiding(verticalInsets: safeAreaInsets).insetBy(dx: 8.0 + layoutMargins.left, dy: 0.0)
if let image = backgroundImageView.image, image.size == bounds.size {
// No op
} else {
backgroundImageView.image = backgroundImageForSize(bounds.size)
}
}
}

View File

@@ -0,0 +1,47 @@
//
// ToolbarButtonContainerView.swift
// App
//
// Created by James Magahern on 8/14/20.
//
import UIKit
class ToolbarButtonContainerView: UIView
{
private var buttonViews: [UIView] = []
public var numberOfButtonViews: Int { buttonViews.count }
func addButtonView(_ button: UIView) {
buttonViews.append(button)
addSubview(button)
setNeedsLayout()
}
func removeAllButtonViews() {
buttonViews.forEach { $0.removeFromSuperview() }
buttonViews.removeAll()
setNeedsLayout()
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let width: CGFloat = buttonViews.reduce(0.0) { (result, button) -> CGFloat in
return result + button.sizeThatFits(size).width
}
return CGSize(width: width, height: size.height)
}
override func layoutSubviews() {
var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height))
for button in buttonViews {
let buttonSize = button.sizeThatFits(bounds.size)
buttonRect.size = CGSize(width: buttonSize.width, height: bounds.height)
button.frame = buttonRect
buttonRect.origin.x += buttonRect.width
}
}
}

View File

@@ -0,0 +1,115 @@
//
// ToolbarView.swift
// App
//
// Created by James Magahern on 8/14/20.
//
import UIKit
class ToolbarView: UIView
{
var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } }
var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } }
let containerView = UIView(frame: .zero)
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
let cancelButton = UIButton(type: .system)
let leadingButtonsView = ToolbarButtonContainerView(frame: .zero)
let trailingButtonsView = ToolbarButtonContainerView(frame: .zero)
convenience init()
{
self.init(frame: .zero)
addSubview(backgroundView)
addSubview(containerView)
containerView.addSubview(leadingButtonsView)
containerView.addSubview(trailingButtonsView)
cancelButton.setTitle("Cancel", for: .normal)
containerView.addSubview(cancelButton)
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.2
layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
layer.shadowRadius = 1.5
}
override func sizeThatFits(_ size: CGSize) -> CGSize
{
return CGSize(width: size.width, height: 44.0)
}
override func layoutSubviews()
{
super.layoutSubviews()
backgroundView.frame = bounds
var containerBounds = bounds
containerBounds.size.height -= safeAreaInsets.bottom
containerView.frame = containerBounds
containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0)
let buttonContainerInset = UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)
var urlBarPadding = CGFloat(8.0)
var urlBarInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
// Cancel button
var cancelButtonSize = cancelButton.sizeThatFits(containerView.bounds.size)
cancelButtonSize.width += (urlBarPadding * 2)
cancelButton.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - cancelButtonSize.width), y: 0),
size: CGSize(width: cancelButtonSize.width + urlBarPadding, height: containerView.bounds.height))
// Leading toolbar buttons
if leadingButtonsView.numberOfButtonViews > 0 {
let leadingContainerSize = leadingButtonsView.sizeThatFits(containerView.bounds.size)
leadingButtonsView.frame = CGRect(origin: .zero, size: leadingContainerSize).inset(by: buttonContainerInset)
urlBarInsets.left = urlBarPadding
} else {
leadingButtonsView.frame = .zero
}
// Trailing toolbar buttons
let trailingContainerSize = trailingButtonsView.sizeThatFits(containerView.bounds.size)
trailingButtonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - trailingContainerSize.width), y: 0), size: trailingContainerSize)
trailingButtonsView.frame = trailingButtonsView.frame.inset(by: buttonContainerInset)
urlBarInsets.right = urlBarPadding
var avoidingSize: CGSize = .zero
if cancelButtonVisible {
cancelButton.alpha = 1.0
trailingButtonsView.alpha = 0.0
avoidingSize = cancelButtonSize
} else {
cancelButton.alpha = 0.0
trailingButtonsView.alpha = 1.0
avoidingSize = trailingContainerSize
}
if let urlBar = urlBar {
let origin = CGPoint(
x: leadingButtonsView.frame.maxX,
y: 0.0
)
urlBar.frame = CGRect(
origin: origin,
size: CGSize(
width: containerView.bounds.width - avoidingSize.width - origin.x,
height: trailingContainerSize.height
)
)
urlBar.frame = urlBar.frame.inset(by: urlBarInsets)
}
}
}

View File

@@ -7,154 +7,20 @@
import UIKit
class ToolbarButtonView: UIView
{
private var buttonPadding = CGFloat(24.0)
private var buttonViews: [UIView] = []
public var numberOfButtonViews: Int { buttonViews.count }
func addButtonView(_ button: UIView) {
buttonViews.append(button)
addSubview(button)
setNeedsLayout()
}
func removeAllButtonViews() {
buttonViews.forEach { $0.removeFromSuperview() }
buttonViews.removeAll()
setNeedsLayout()
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let width: CGFloat = buttonViews.reduce(buttonPadding / 2.0) { (result, button) -> CGFloat in
return result + button.sizeThatFits(size).width + buttonPadding
}
return CGSize(width: width, height: size.height)
}
override func layoutSubviews() {
var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height))
for button in buttonViews {
let buttonSize = button.sizeThatFits(bounds.size)
buttonRect.size = CGSize(width: buttonSize.width + buttonPadding, height: bounds.height)
button.frame = buttonRect
buttonRect.origin.x += buttonRect.width
}
}
}
class ToolbarView: UIView
{
var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } }
var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } }
let containerView = UIView(frame: .zero)
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
let cancelButton = UIButton(type: .system)
let leadingButtonsView = ToolbarButtonView(frame: .zero)
let trailingButtonsView = ToolbarButtonView(frame: .zero)
convenience init()
{
self.init(frame: .zero)
addSubview(backgroundView)
addSubview(containerView)
containerView.addSubview(leadingButtonsView)
containerView.addSubview(trailingButtonsView)
cancelButton.setTitle("Cancel", for: .normal)
containerView.addSubview(cancelButton)
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.2
layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
layer.shadowRadius = 1.5
}
override func sizeThatFits(_ size: CGSize) -> CGSize
{
return CGSize(width: size.width, height: 44.0)
}
override func layoutSubviews()
{
super.layoutSubviews()
backgroundView.frame = bounds
var containerBounds = bounds
containerBounds.size.height -= safeAreaInsets.bottom
containerView.frame = containerBounds
containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0)
// Cancel button
let urlBarPadding: CGFloat = 8.0
var cancelButtonSize = cancelButton.sizeThatFits(containerView.bounds.size)
cancelButtonSize.width += (urlBarPadding * 2)
cancelButton.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - cancelButtonSize.width), y: 0),
size: CGSize(width: cancelButtonSize.width + urlBarPadding, height: containerView.bounds.height))
// Leading toolbar buttons
if leadingButtonsView.numberOfButtonViews > 0 {
let leadingContainerSize = leadingButtonsView.sizeThatFits(containerView.bounds.size)
leadingButtonsView.frame = CGRect(origin: .zero, size: leadingContainerSize)
} else {
leadingButtonsView.frame = .zero
}
// Trailing toolbar buttons
let trailingContainerSize = trailingButtonsView.sizeThatFits(containerView.bounds.size)
trailingButtonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - trailingContainerSize.width) + urlBarPadding, y: 0), size: trailingContainerSize)
var avoidingSize: CGSize = .zero
if cancelButtonVisible {
cancelButton.alpha = 1.0
trailingButtonsView.alpha = 0.0
avoidingSize = cancelButtonSize
} else {
cancelButton.alpha = 0.0
trailingButtonsView.alpha = 1.0
avoidingSize = trailingContainerSize
}
if let urlBar = urlBar {
let origin = CGPoint(
x: leadingButtonsView.frame.maxX,
y: 0.0
)
urlBar.frame = CGRect(
origin: origin,
size: CGSize(
width: containerView.bounds.width - avoidingSize.width - origin.x,
height: containerView.bounds.height
)
)
}
}
}
class ToolbarViewController: UIViewController
{
let urlBar = URLBar()
let toolbarView = ToolbarView()
let scriptControllerIconView = ScriptControllerIconView()
let shareButton = UIButton(frame: .zero)
let darkModeButton = UIButton(frame: .zero)
let windowButton = UIButton(frame: .zero)
let backButton = UIButton(frame: .zero)
let forwardButton = UIButton(frame: .zero)
let newTabButton = UIButton(frame: .zero)
let shareButton = ReliefButton()
let darkModeButton = ReliefButton()
let windowButton = ReliefButton()
let newTabButton = ReliefButton()
let backButton = ReliefButton()
let forwardButton = ReliefButton()
let navigationSegmentedButton = SegmentedReliefButton(children: [])
var darkModeEnabled: Bool = false {
didSet {
@@ -189,6 +55,9 @@ class ToolbarViewController: UIViewController
// Forward button
forwardButton.setImage(UIImage(systemName: "chevron.right"), for: .normal)
// Navigation control
navigationSegmentedButton.children = [ backButton, forwardButton ]
// New tab button
newTabButton.setImage(UIImage(systemName: "plus"), for: .normal)
@@ -214,18 +83,27 @@ class ToolbarViewController: UIViewController
toolbarView.leadingButtonsView.removeAllButtonViews()
toolbarView.trailingButtonsView.removeAllButtonViews()
let spacerView = { () -> SpacerView in
SpacerView(space: 24.0)
}
// Setup toolbar based on trait collection
if traitCollection.horizontalSizeClass == .compact {
toolbarView.trailingButtonsView.addButtonView(darkModeButton)
toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView)
toolbarView.trailingButtonsView.addButtonView(windowButton)
} else {
toolbarView.leadingButtonsView.addButtonView(backButton)
toolbarView.leadingButtonsView.addButtonView(forwardButton)
toolbarView.leadingButtonsView.addButtonView(navigationSegmentedButton)
toolbarView.leadingButtonsView.addButtonView(spacerView())
toolbarView.trailingButtonsView.addButtonView(shareButton)
toolbarView.trailingButtonsView.addButtonView(spacerView())
toolbarView.trailingButtonsView.addButtonView(darkModeButton)
toolbarView.trailingButtonsView.addButtonView(shareButton)
toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView)
toolbarView.trailingButtonsView.addButtonView(spacerView())
toolbarView.trailingButtonsView.addButtonView(newTabButton)
toolbarView.trailingButtonsView.addButtonView(windowButton)
}
@@ -239,3 +117,16 @@ class ToolbarViewController: UIViewController
fatalError("init(coder:) has not been implemented")
}
}
class SpacerView: UIView {
internal var space: CGFloat = 0
convenience init(space: CGFloat) {
self.init(frame: .zero)
self.space = space
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
CGSize(width: space, height: size.height)
}
}

View File

@@ -24,6 +24,7 @@ class URLBar: UIView
}
private let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
private let shadowView = UIView(frame: .zero)
private let progressIndicatorView = ProgressIndicatorView()
private let fadeMaskView = UIImageView(frame: .zero)
@@ -40,7 +41,7 @@ class URLBar: UIView
backgroundView.layer.masksToBounds = true
backgroundView.layer.cornerRadius = backgroundCornerRadius
backgroundView.layer.borderWidth = 1
backgroundView.layer.borderColor = UIColor.systemFill.cgColor
backgroundView.layer.borderColor = UIColor.secondarySystemFill.cgColor
backgroundView.isUserInteractionEnabled = false
addSubview(backgroundView)
@@ -166,6 +167,7 @@ class URLBar: UIView
override func layoutSubviews() {
super.layoutSubviews()
backgroundView.frame = bounds
shadowView.frame = bounds
progressIndicatorView.frame = backgroundView.contentView.bounds
textField.frame = bounds.insetBy(dx: 6.0, dy: 0)

View File

@@ -29,4 +29,26 @@ extension CGRect
return rect
}
public func centeredX(inRect: CGRect) -> CGRect {
var rect = self
rect.origin.x = CGRound((inRect.width - rect.width) / 2.0)
return rect
}
public func centered(inRect: CGRect) -> CGRect {
self.centeredX(inRect: inRect).centeredY(inRect: inRect)
}
}
extension CGSize
{
public func extendingBy(dw: CGFloat, dh: CGFloat) -> CGSize {
var ourSize = self
ourSize.width += dw
ourSize.height += dh
return ourSize
}
}