From 69b57779d3ead3a31033bc309cda8bbb44af8ea1 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 7 Sep 2026 21:18:39 +0000 Subject: [PATCH 1/3] Even out and space the status screen toolbar icons On iOS 26 the five toolbar items share one Liquid Glass background with no spacing between them. Any space item inserted between them splits that shared background into separate pills, so the gaps here are transparent padding baked into each rendered icon instead, which widens the spacing while leaving the single pill intact. Sizing normalizes on each icon's visible artwork rather than its canvas. The stock assets pad themselves by very different amounts -- bolus has essentially none, carbs has ~3.7pt top and bottom, and settings is a 25x25 gear centered on a 25x40 canvas -- so matching canvas sizes leaves the glyphs visibly uneven and gives settings a noticeably taller pill. The ink bounds were measured by rasterizing the assets at 600dpi and taking the alpha bounding box; artwork is centered in every one to within 0.6pt, so scaling alone aligns them. Also records the pre-meal item's index at setup instead of hardcoding it per OS version, since the two layouts place it differently. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019hVEziK3QFghqmf2Hq4YAw --- .../StatusTableViewController.swift | 99 ++++++++++++++++--- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 37751e093..22a6f5937 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -273,9 +273,9 @@ final class StatusTableViewController: LoopChartsTableViewController { deviceManager.pumpManagerHUDProvider?.visible = active && onscreen } - private lazy var carbEntryButton = makeToolbarButton(imageNamed: "carbs", tintColor: .carbTintColor, action: #selector(userTappedAddCarbs)) - private lazy var bolusButton = makeToolbarButton(imageNamed: "bolus", tintColor: .insulinTintColor, action: #selector(presentBolusScreen)) - private lazy var settingsButton = makeToolbarButton(imageNamed: "settings", tintColor: .secondaryLabel, action: #selector(onSettingsTapped)) + private lazy var carbEntryButton = makeToolbarButton(imageNamed: "carbs", tintColor: .carbTintColor, action: #selector(userTappedAddCarbs), ink: ToolbarLayout.Ink.carbs) + private lazy var bolusButton = makeToolbarButton(imageNamed: "bolus", tintColor: .insulinTintColor, action: #selector(presentBolusScreen), ink: ToolbarLayout.Ink.bolus) + private lazy var settingsButton = makeToolbarButton(imageNamed: "settings", tintColor: .secondaryLabel, action: #selector(onSettingsTapped), ink: ToolbarLayout.Ink.settings) private lazy var workoutButton: UIButton = { let button = UIButton(type: .system) @@ -285,15 +285,54 @@ final class StatusTableViewController: LoopChartsTableViewController { return button }() - private func makeToolbarButton(imageNamed name: String, tintColor: UIColor, action: Selector) -> UIButton { + private func makeToolbarButton(imageNamed name: String, tintColor: UIColor, action: Selector, ink: CGSize) -> UIButton { let button = UIButton(type: .system) - button.setImage(UIImage(named: name), for: .normal) + button.setImage(UIImage(named: name)?.toolbarIcon(ink: ink), for: .normal) button.tintColor = tintColor button.addTarget(self, action: action, for: .touchUpInside) button.constrainToToolbarIconSize() return button } + /// Sizing for the icons in the bottom toolbar. + /// + /// On iOS 26 the toolbar items share one Liquid Glass background, and any + /// space item between them splits that background into separate pills. So + /// the gaps here are transparent padding baked into each rendered icon, + /// which widens the spacing while leaving the shared pill intact. + /// + /// `fileprivate` so the UIImage helper at the bottom of this file can read it. + fileprivate enum ToolbarLayout { + /// Height every icon's *visible artwork* is scaled to, in points. The + /// stock assets pad their canvases by wildly different amounts, so + /// matching canvases (the obvious approach) leaves the glyphs uneven. + static let inkHeight: CGFloat = 30 + /// Transparent space kept to each side of the artwork; this is what + /// separates the icons within the shared pill. + static let iconHorizontalPadding: CGFloat = 10 + /// Transparent space kept above and below the artwork in each item. + static let iconVerticalMargin: CGFloat = 4 + + /// Bounding box of the visible artwork inside each asset, in the asset's + /// own points. Measured by rasterizing the PDFs at 600dpi and taking the + /// alpha bounding box; the artwork is centered in every one of them to + /// within 0.6pt, so scaling alone aligns them. If an upstream asset is + /// redrawn these go stale, and that icon renders slightly off-size -- + /// remeasure rather than nudging `inkHeight`. + enum Ink { + static let carbs = CGSize(width: 25.52, height: 21.57) + static let bolus = CGSize(width: 20.33, height: 23.32) + static let preMeal = CGSize(width: 25.52, height: 21.57) + static let workout = CGSize(width: 27.72, height: 23.88) + /// 25x25 gear centered on a 25x40 canvas. + static let settings = CGSize(width: 25.00, height: 25.00) + } + } + + /// Position of the pre-meal item within `toolbarItems`, recorded at setup + /// because it differs between the iOS 26 and legacy layouts. + private var preMealItemIndex: Int = 1 + private func setupToolbarItems() { let carbs = UIBarButtonItem(customView: carbEntryButton) let bolus = UIBarButtonItem(customView: bolusButton) @@ -308,7 +347,7 @@ final class StatusTableViewController: LoopChartsTableViewController { func flexibleSpace() -> UIBarButtonItem { UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) } - + if #available(iOS 26, *) { toolbarItems = [carbs, preMeal, bolus, workout, settings] } else { @@ -324,6 +363,10 @@ final class StatusTableViewController: LoopChartsTableViewController { settings ] } + + // The two layouts put pre-meal at different indices, so record where it + // actually landed rather than hardcoding it. + preMealItemIndex = toolbarItems?.firstIndex { $0 === preMeal } ?? 1 } private func updateToolbarItems() { @@ -335,13 +378,7 @@ final class StatusTableViewController: LoopChartsTableViewController { bolusButton.isEnabled = isPumpOnboarded settingsButton.accessibilityLabel = NSLocalizedString("Settings", comment: "The label of the settings button") - let preMealIndex: Int - if #available(iOS 26, *) { - preMealIndex = 1 - } else { - preMealIndex = 2 - } - toolbarItems![preMealIndex] = createPreMealButtonItem(selected: preMealMode == true && preMealModeAllowed, isEnabled: preMealModeAllowed) + toolbarItems![preMealItemIndex] = createPreMealButtonItem(selected: preMealMode == true && preMealModeAllowed, isEnabled: preMealModeAllowed) updateWorkoutButton(selected: workoutMode == true && workoutModeAllowed, isEnabled: workoutModeAllowed) } @@ -1478,7 +1515,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } private func createPreMealButtonItem(selected: Bool, isEnabled: Bool) -> UIBarButtonItem { - let item = UIBarButtonItem(image: UIImage.preMealImage(selected: selected), style: .plain, target: self, action: #selector(premealButtonTapped(_:))) + let item = UIBarButtonItem(image: UIImage.preMealImage(selected: selected)?.toolbarIcon(ink: ToolbarLayout.Ink.preMeal), style: .plain, target: self, action: #selector(premealButtonTapped(_:))) item.accessibilityLabel = NSLocalizedString("Pre-Meal Targets", comment: "The label of the pre-meal mode toggle button") if selected { @@ -1495,7 +1532,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } private func updateWorkoutButton(selected: Bool, isEnabled: Bool) { - workoutButton.setImage(UIImage.workoutImage(selected: selected), for: .normal) + workoutButton.setImage(UIImage.workoutImage(selected: selected)?.toolbarIcon(ink: ToolbarLayout.Ink.workout), for: .normal) workoutButton.accessibilityLabel = NSLocalizedString("Workout Targets", comment: "The label of the workout mode toggle button") if selected { @@ -2093,6 +2130,38 @@ private extension UIButton { } } +private extension UIImage { + /// Scales the image so its visible artwork is `inkHeight` tall, then centers + /// it on a canvas sized to that artwork plus uniform padding. Normalizing on + /// ink rather than canvas is what makes the five icons look the same size: + /// the assets pad themselves by anywhere from 0 to 7.5pt. + /// + /// The toolbar icons are vector PDFs with `preserves-vector-representation`, + /// so scaling up stays crisp. Transparent canvas that falls outside the + /// output is simply clipped. + /// + /// - Parameter ink: bounding box of the visible artwork, in the source + /// image's own points. See `ToolbarLayout.Ink`. + func toolbarIcon(ink: CGSize) -> UIImage { + let layout = StatusTableViewController.ToolbarLayout.self + guard ink.height > 0, ink.width > 0 else { return self } + + let scale = layout.inkHeight / ink.height + let drawSize = CGSize(width: size.width * scale, height: size.height * scale) + let canvas = CGSize(width: (ink.width * scale) + (layout.iconHorizontalPadding * 2), + height: layout.inkHeight + (layout.iconVerticalMargin * 2)) + let origin = CGPoint(x: (canvas.width - drawSize.width) / 2, + y: (canvas.height - drawSize.height) / 2) + + let format = UIGraphicsImageRendererFormat.preferred() + format.opaque = false + let rendered = UIGraphicsImageRenderer(size: canvas, format: format).image { _ in + draw(in: CGRect(origin: origin, size: drawSize)) + } + return rendered.withRenderingMode(.alwaysTemplate) + } +} + extension UIAlertController { func addActivityIndicator() { let frame = CGRect(x: 0, y: 0, width: 40, height: 40) From 43b4f5f1466ab6c89f67864198fe99e9d1093c97 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 8 Sep 2026 01:07:09 -0500 Subject: [PATCH 2/3] Measure icon ink at runtime and clear the 44pt tap target Two follow-ups to the icon sizing in this branch. Replace the ToolbarLayout.Ink table with an alpha-channel scan of each image, so toolbarIcon() no longer takes an ink: argument and the five measured constants go away. Same result, but it cannot go stale if an asset is redrawn. Measuring also yields the ink's origin, so the icon is centered on its artwork rather than on its canvas -- these assets are centered to within 0.6pt, but the code no longer depends on that. Raise iconVerticalMargin from 4 to 7 so each rendered canvas is 44pt tall. The horizontal padding already put every item past 44pt wide, but height was inkHeight + 8 = 38pt, under the HIG minimum, and a small tap target is part of what people are reporting. Checked in portrait and landscape on an iPhone 16 Pro. --- .../StatusTableViewController.swift | 100 ++++++++++++------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 22a6f5937..6d88d0f34 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -273,9 +273,9 @@ final class StatusTableViewController: LoopChartsTableViewController { deviceManager.pumpManagerHUDProvider?.visible = active && onscreen } - private lazy var carbEntryButton = makeToolbarButton(imageNamed: "carbs", tintColor: .carbTintColor, action: #selector(userTappedAddCarbs), ink: ToolbarLayout.Ink.carbs) - private lazy var bolusButton = makeToolbarButton(imageNamed: "bolus", tintColor: .insulinTintColor, action: #selector(presentBolusScreen), ink: ToolbarLayout.Ink.bolus) - private lazy var settingsButton = makeToolbarButton(imageNamed: "settings", tintColor: .secondaryLabel, action: #selector(onSettingsTapped), ink: ToolbarLayout.Ink.settings) + private lazy var carbEntryButton = makeToolbarButton(imageNamed: "carbs", tintColor: .carbTintColor, action: #selector(userTappedAddCarbs)) + private lazy var bolusButton = makeToolbarButton(imageNamed: "bolus", tintColor: .insulinTintColor, action: #selector(presentBolusScreen)) + private lazy var settingsButton = makeToolbarButton(imageNamed: "settings", tintColor: .secondaryLabel, action: #selector(onSettingsTapped)) private lazy var workoutButton: UIButton = { let button = UIButton(type: .system) @@ -285,9 +285,9 @@ final class StatusTableViewController: LoopChartsTableViewController { return button }() - private func makeToolbarButton(imageNamed name: String, tintColor: UIColor, action: Selector, ink: CGSize) -> UIButton { + private func makeToolbarButton(imageNamed name: String, tintColor: UIColor, action: Selector) -> UIButton { let button = UIButton(type: .system) - button.setImage(UIImage(named: name)?.toolbarIcon(ink: ink), for: .normal) + button.setImage(UIImage(named: name)?.toolbarIcon(), for: .normal) button.tintColor = tintColor button.addTarget(self, action: action, for: .touchUpInside) button.constrainToToolbarIconSize() @@ -311,22 +311,10 @@ final class StatusTableViewController: LoopChartsTableViewController { /// separates the icons within the shared pill. static let iconHorizontalPadding: CGFloat = 10 /// Transparent space kept above and below the artwork in each item. - static let iconVerticalMargin: CGFloat = 4 - - /// Bounding box of the visible artwork inside each asset, in the asset's - /// own points. Measured by rasterizing the PDFs at 600dpi and taking the - /// alpha bounding box; the artwork is centered in every one of them to - /// within 0.6pt, so scaling alone aligns them. If an upstream asset is - /// redrawn these go stale, and that icon renders slightly off-size -- - /// remeasure rather than nudging `inkHeight`. - enum Ink { - static let carbs = CGSize(width: 25.52, height: 21.57) - static let bolus = CGSize(width: 20.33, height: 23.32) - static let preMeal = CGSize(width: 25.52, height: 21.57) - static let workout = CGSize(width: 27.72, height: 23.88) - /// 25x25 gear centered on a 25x40 canvas. - static let settings = CGSize(width: 25.00, height: 25.00) - } + /// Sized so each item clears the 44pt minimum tap target: the rendered + /// canvas is `inkHeight` plus this margin top and bottom. Horizontally + /// the padding above already puts every item past 44pt. + static let iconVerticalMargin: CGFloat = 7 } /// Position of the pre-meal item within `toolbarItems`, recorded at setup @@ -1515,7 +1503,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } private func createPreMealButtonItem(selected: Bool, isEnabled: Bool) -> UIBarButtonItem { - let item = UIBarButtonItem(image: UIImage.preMealImage(selected: selected)?.toolbarIcon(ink: ToolbarLayout.Ink.preMeal), style: .plain, target: self, action: #selector(premealButtonTapped(_:))) + let item = UIBarButtonItem(image: UIImage.preMealImage(selected: selected)?.toolbarIcon(), style: .plain, target: self, action: #selector(premealButtonTapped(_:))) item.accessibilityLabel = NSLocalizedString("Pre-Meal Targets", comment: "The label of the pre-meal mode toggle button") if selected { @@ -1532,7 +1520,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } private func updateWorkoutButton(selected: Bool, isEnabled: Bool) { - workoutButton.setImage(UIImage.workoutImage(selected: selected)?.toolbarIcon(ink: ToolbarLayout.Ink.workout), for: .normal) + workoutButton.setImage(UIImage.workoutImage(selected: selected)?.toolbarIcon(), for: .normal) workoutButton.accessibilityLabel = NSLocalizedString("Workout Targets", comment: "The label of the workout mode toggle button") if selected { @@ -2131,27 +2119,71 @@ private extension UIButton { } private extension UIImage { + /// Bounds of the visible artwork within the image, in points, ignoring any + /// transparent margin around it. + /// + /// The stock assets pad themselves by wildly different amounts -- bolus has + /// essentially none, carbs has ~3.7pt top and bottom, and settings is a 25x25 + /// gear centered on a 25x40 canvas -- so sizing on the canvas leaves the + /// glyphs visibly uneven. Measuring here rather than carrying a table of + /// constants keeps this correct if an asset is ever redrawn. + func inkBounds() -> CGRect? { + guard let cgImage = cgImage, cgImage.width > 0, cgImage.height > 0 else { return nil } + + let width = cgImage.width + let height = cgImage.height + var pixels = [UInt8](repeating: 0, count: width * height * 4) + guard let context = CGContext(data: &pixels, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: width * 4, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + else { return nil } + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + + var minX = width, maxX = -1, minY = height, maxY = -1 + for y in 0.. 0 { + minX = min(minX, x) + maxX = max(maxX, x) + minY = min(minY, y) + maxY = max(maxY, y) + } + } + guard maxX >= minX, maxY >= minY else { return nil } + + // CGContext draws bottom-up; flip the vertical range into UIKit's coordinates. + let scaleX = size.width / CGFloat(width) + let scaleY = size.height / CGFloat(height) + return CGRect(x: CGFloat(minX) * scaleX, + y: CGFloat(height - 1 - maxY) * scaleY, + width: CGFloat(maxX - minX + 1) * scaleX, + height: CGFloat(maxY - minY + 1) * scaleY) + } + /// Scales the image so its visible artwork is `inkHeight` tall, then centers - /// it on a canvas sized to that artwork plus uniform padding. Normalizing on - /// ink rather than canvas is what makes the five icons look the same size: - /// the assets pad themselves by anywhere from 0 to 7.5pt. + /// that artwork on a canvas sized to it plus uniform padding. Normalizing on + /// ink rather than canvas is what makes the five icons look the same size. /// /// The toolbar icons are vector PDFs with `preserves-vector-representation`, /// so scaling up stays crisp. Transparent canvas that falls outside the /// output is simply clipped. - /// - /// - Parameter ink: bounding box of the visible artwork, in the source - /// image's own points. See `ToolbarLayout.Ink`. - func toolbarIcon(ink: CGSize) -> UIImage { + func toolbarIcon() -> UIImage { let layout = StatusTableViewController.ToolbarLayout.self - guard ink.height > 0, ink.width > 0 else { return self } + let ink = inkBounds() ?? CGRect(origin: .zero, size: size) + guard ink.width > 0, ink.height > 0 else { return self } let scale = layout.inkHeight / ink.height let drawSize = CGSize(width: size.width * scale, height: size.height * scale) - let canvas = CGSize(width: (ink.width * scale) + (layout.iconHorizontalPadding * 2), + let scaledInk = ink.applying(CGAffineTransform(scaleX: scale, y: scale)) + let canvas = CGSize(width: scaledInk.width + (layout.iconHorizontalPadding * 2), height: layout.inkHeight + (layout.iconVerticalMargin * 2)) - let origin = CGPoint(x: (canvas.width - drawSize.width) / 2, - y: (canvas.height - drawSize.height) / 2) + + // Offset the whole image so that its ink, not its canvas, ends up centered. + let origin = CGPoint(x: (canvas.width - scaledInk.width) / 2 - scaledInk.minX, + y: (canvas.height - scaledInk.height) / 2 - scaledInk.minY) let format = UIGraphicsImageRendererFormat.preferred() format.opaque = false From 6f03460bd18c50596f75b6ce51f7148ba78dc5a6 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 8 Sep 2026 13:37:34 -0500 Subject: [PATCH 3/3] Normalize the toolbar icon assets and drop the sizing code Put all seven toolbar icon assets on a common 40x40pt canvas with the artwork 30pt tall, so nothing in code needs to know how each asset is padded. The PDFs stay vector: each page is re-placed with a scale and translate through CoreGraphics, not rasterized. The settings gear is the one PNG and is upscaled 1.2x from its @3x source; its compact-height variant is dropped so landscape loads the same asset as portrait. The two selected states are inverted badges, a plate filling the canvas with the glyph knocked out, not line art. Their alpha bounds are the plate, so any scheme that sizes on ink gets them wrong: sizing on the partner's ink (the constants table) grew the pre-meal plate to 40pt on a 38pt item and clipped its corners; sizing on their own ink (the runtime scan) shrank the glyph 20-27% when toggled on. Each badge now uses its unselected partner's transform, so toggling swaps in place with the plate at its natural size. With the assets normalized, toolbarIcon() only centers the image on the item canvas: 44pt for the minimum tap target, plus horizontal room between items since on iOS 26 a space item would split the shared Liquid Glass background. ToolbarLayout.Ink, inkBounds(), inkHeight and the padding constants are gone. --- .../Pre-Meal Selected.pdf | Bin 4645 -> 4648 bytes .../Pre-Meal.imageset/Pre-Meal.pdf | Bin 4506 -> 4483 bytes .../bolus.imageset/bolus.pdf | Bin 3946 -> 3804 bytes .../carbs.imageset/Meal.pdf | Bin 4523 -> 4554 bytes .../settings.imageset/Contents.json | 43 +++----- .../settings.imageset/settings.png | Bin 820 -> 1074 bytes .../settings.imageset/settings@2x.png | Bin 1898 -> 2696 bytes .../settings.imageset/settings@3x.png | Bin 2921 -> 5461 bytes .../settings.imageset/settings_compact@2x.png | Bin 1424 -> 0 bytes .../settings.imageset/settings_compact@3x.png | Bin 2223 -> 0 bytes .../workout-selected.pdf | Bin 4299 -> 4184 bytes .../workout.imageset/workout.pdf | Bin 4984 -> 4865 bytes .../StatusTableViewController.swift | 99 +++--------------- 13 files changed, 26 insertions(+), 116 deletions(-) delete mode 100644 Loop/DerivedAssetsBase.xcassets/settings.imageset/settings_compact@2x.png delete mode 100644 Loop/DerivedAssetsBase.xcassets/settings.imageset/settings_compact@3x.png diff --git a/Loop/DerivedAssetsBase.xcassets/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf b/Loop/DerivedAssetsBase.xcassets/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf index 8c285da0df0a223013d124f21467d9c3aefef557..a55a507ca28647c75489b92e86f414417e004186 100644 GIT binary patch delta 1762 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 diff --git a/Loop/DerivedAssetsBase.xcassets/Pre-Meal.imageset/Pre-Meal.pdf b/Loop/DerivedAssetsBase.xcassets/Pre-Meal.imageset/Pre-Meal.pdf index 0992a4a2860b615d49418e1a454975c881f1b844..76620abf024cd7119bf834053055ae96f289e75d 100644 GIT binary patch delta 1617 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 diff --git a/Loop/DerivedAssetsBase.xcassets/bolus.imageset/bolus.pdf b/Loop/DerivedAssetsBase.xcassets/bolus.imageset/bolus.pdf index c9e69b5cfc093e82080b2390889ae93b949a7148..2b83db1d1e6207ae2ab0255b0386b47245e73eb4 100644 GIT binary patch delta 918 zcmaixT}TvB6vvyivPn0>kI>K#*w%{R+`Y52Gm2R5uBN8Be&mC@KFsc1ZBut_W;QLz zMax16N(ND|dhpGj%pQ^~Oe2KA-iio`pcknJqlbbL?C$!}he4OiB z`&I_a0|GU0jx8=W*O-hbS53lg##yjS5|os%x}g!dlkH&!yfc=0l)UsKUCfnlck|!& zd9U~Ga0M!c*o4`echV+|3H;{1J~HF$dfs1gpl>{7a_IXJt7(3D6r4}B9QI#(IEVT_ zW$r0GU$*>y>23Aym-$mOi<5UdlAiPprlDs8uhU15Bt%!diOnN8Q1xzXbtb>PE~%R= zG=6?+n>sU10_pc|Oo&ah@(Ua)x*E)_8vVL5+)*^Ra3yIqnwb5;wzDGf)pAxS_^R&?x5BzU3LX#*1A9IIr9mKShm%4Zt*C4_rOb)rb}(Iylw^ zw;DERUWXX8`WT;$f(~<#L3@P_70g~HI1IQ?RLpJQNS6m)M92aImJ3~HAD%?F0}`Ih$bAd-2$9jjnH20z?}*y zI{0W5YpUh9wnTF!8Ft0pm~+g*sGAPc2&rLDtRb7H2qWx7g-EhXJQjpzgpSdjM`>uP z5(yFwY@KqjwgH-mrqgGDodrSx2EWY}33Vudg>uQ^&x5*%tQnWU>nV=|>M`V1Yt!+gttoq31EQ_ eA{^jx3H$#m>od{~N-gf3cZi6!TFYw78TKbM=M+Bx delta 1022 zcmah{T}TvB6lU|q#_rOaxhN2#hYFRzupWwvH0`OPL`otHgun;E=pnjy*Y$@Ux(v*{-~GXBqj@_r9`m)c>iXpvl^K`seiPUsd{9uz9t3LFzeccY)%&2?SD$JE-m7dbrn)cxvrYB`MgnTBC3W31egkFeSuw)s03kH!>A}R zd-#G#21CTf97<#63`hxa8dJHYN#bC|q%Sjfh`LyVTQQ1VOuDTY67B1WGqDsq{^3!I zn8{mXDV0)~Wpkxe#KfrGu{Vh+8$eBCj`G=JqBKOp8nZXO_c4Rr3L;b$oi15+U`fTq p;Y6VhhfF%8a7e_W=n?);fp$J1F`fCG5|>KEC6tyP$qLj9e*q}!CF1}9 diff --git a/Loop/DerivedAssetsBase.xcassets/carbs.imageset/Meal.pdf b/Loop/DerivedAssetsBase.xcassets/carbs.imageset/Meal.pdf index 5e1c948daddf721ee3dd86e653fbe87d78ad98be..40d01e73b29b8efe497b153f5637bc5515165f20 100644 GIT binary patch delta 1555 zcmah}X;4#F6b6C_VILM{zoH0KATKXFCa4KZ8I+wMTWLsMLJ$(kCZL$2P#FX?eW1vU zvRJW-C{wgF+FGS(fFdF;R4SkrL8^dQMpP7d5fKSw>3|ATK|lajDw80bENqVc;kXNuWQy6uE4%L)HjeFkc};ES zLW%;u)P$B)3V3$&`EElOR*4_lD$Y6zbJy?E>(PH1Tr*x)C@l<&yf)dFcs%}_>aagW zPBESKeTPd%8tw=&jK-E$*&kIoBI{>rXJO}+>Rb7PN~S2*?CNCY>I05F@s^o2N9?0r zXK$2|Ul?c564l*L`J@>yu=L3>-WE~7!{^=i?%r(s7Ky(Tj#Skv z209uzoO;^%@yi3_ChE}XMDm2G{)8$na?<*5^_JqjYA@dio6uzgo;jJ&XKT3_7EAML@b=<$MZP)1C^R?$Z6{(v`d{&gyv9$Z&}5urY5!ok0lVNe>FvBtLp+;=iS<=uW;yZNtK` z3L$qRr3>Rp=+}8iU+K}YSaL;7ScfX8(xTI7qfdyy;hyGx-57hCm7!kRvZ|XPT+*XF z*=^f%#-fyHbQSL-nyO9TF{D}|AlMjI*`ap61yOJC)Tw>N0;ReR-GKdh2bq zd@IK*kM3-xH+sB~=IGX5T$GX)7n_(poW+UHEodrH5!z?=_a~T} z)p$5xcd;}|OYL}UDt_#6_c?RN&|udK!|&zABlV|ER`d`~u1!#|8ySqWGwW;l^zwag zHm`T6E}g5oe!+NU6#Gg;ORUu+W{)IzVC*wPYRS&s(i5E0e!=Wu>0j$=L*?hMSKu%+ zyHd)-X({5DWg!s5^>PjIT0*_hwwWGOmc7O$=IwC(ynd=n(_1v#!Ula|;0nW9xHf6SR_M4GAYpUFAhH)|>>wMUN0Dwke>`@KI z9j0h4i|hrsaBVJbL#7Ooh&VC?-~thakc&izFu?~_!(=?H@^f!lXBdceBE3&d0=Q5X zi^-89JfJHV4N}kv16N22aGMaGki!(`fFwxkAnGIq;8kB2Z~)kd;R|9)kYEN74@B<9Iwo zqkdw;6Sc$rIF3N0d{U1H5lE%IMFi_({{ah@So{D0 delta 1515 zcmX@5yjppJn1qRffkJ*#7MG2Ug1%2`UV2G}f+js&2iTGc)N{R+FvNQ|+@h5?kj@&HdJ1`zxhB zGT(Mh^7f4n|G!;b+qB``VrvG3I@{`RF#J0{MwjdeV*s6lq2PNA*} zXPd#M7XmREQ*E^$`8YJrE5H4;#3-;i*HZdukEQT}s0Kmbe&e&Jw)0f{ux5SsU&N-q zj!pf3{`U}rXDbA4-K94xs;+Z8>GEfuqR)jAwr0Vu{l0T^ zoww=c_vO8L&*IJBlISS-4o+xw&McdLaOnoJw{F3Zh$ z{hQ}b`CnVP!)d`^%zuCQyovvV-Z^i-2=P6kQ6K-#RoEgU9C1^)rEg-g?B;Z5_+{LgQKCnv3r z5xBqS!=_C-F(KE5j9xEIcsb>b;ug~d7B;)KwaNJl`LQR@k~3UjS5W?_&GLNAT*DoP zM=sYDls-6k_2AnB@^2XgZTg7p%zxGhKf{4v7|C;m3^Z&6je7+LmHTfT(6tkI$ z(PV9Yxyc)tIc<$WNmM~UD784hv?w{XSiuY;;GCaVlA2ditN=>ZKn1?3DVd2*`4tM$ z20#HLOCXBnn(V+LHu)Q~Dx>*i85U^{3y@MSJG;r1{IZi{SY%8rfdXh*!Vsh#Xr7;f zv4Xy1PDyG}USdhAg1$>?S!Qx-kh>EwyKFwd62K(}RteH=WM*i@m713VVw+DE=bchUNz5Kx|}SVr&^@V4w~}2$c#!h_DGL%1UBl4o$s85$U1*l%oZh@sBd!W8IR1681I zghP!j4KXY+wluSxtS2Z_ZvsrG`T4-O1)2{^L!NnQ`M`*Vuw8&rZDWyakz{OUXl7xO zXknOUk(6j@Vw{?sY-VO@Vs4sbY+}bnScyU`C>0c!0OJ@OuqFl;W?ZVOuKrAJTmUDj B8eRYZ diff --git a/Loop/DerivedAssetsBase.xcassets/settings.imageset/Contents.json b/Loop/DerivedAssetsBase.xcassets/settings.imageset/Contents.json index d5ccdb99c..6444f236f 100644 --- a/Loop/DerivedAssetsBase.xcassets/settings.imageset/Contents.json +++ b/Loop/DerivedAssetsBase.xcassets/settings.imageset/Contents.json @@ -1,40 +1,23 @@ { - "images" : [ + "images": [ { - "idiom" : "universal", - "filename" : "settings.png", - "scale" : "1x" + "idiom": "universal", + "filename": "settings.png", + "scale": "1x" }, { - "idiom" : "universal", - "filename" : "settings@2x.png", - "scale" : "2x" + "idiom": "universal", + "filename": "settings@2x.png", + "scale": "2x" }, { - "idiom" : "universal", - "filename" : "settings@3x.png", - "scale" : "3x" - }, - { - "idiom" : "universal", - "scale" : "1x", - "height-class" : "compact" - }, - { - "idiom" : "universal", - "filename" : "settings_compact@2x.png", - "height-class" : "compact", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "settings_compact@3x.png", - "height-class" : "compact", - "scale" : "3x" + "idiom": "universal", + "filename": "settings@3x.png", + "scale": "3x" } ], - "info" : { - "version" : 1, - "author" : "xcode" + "info": { + "version": 1, + "author": "xcode" } } \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/settings.imageset/settings.png b/Loop/DerivedAssetsBase.xcassets/settings.imageset/settings.png index 14fc3623dbe31fa52209545f2dc65e0831f20a16..1f2e2d2dcfacbb991e59665bd6c4a018a88c6ae3 100644 GIT binary patch literal 1074 zcmV-21kL-2P)BPq;0B;|!M)arN`2@!di zguEd0(8@ftg$-ktiP)TT-}mw6d)43Fxp(gSoU?c5S6A2dzpnrFyM9mK-~ao+n3PGG zlzk?BInTxpEJY1HSb!gKe#_jhrKgLzwVY4INvNY2GjS+x!Z4b+0kd#a=G1Wn=0#6! zA|q8rrBeD?A~q-E3u9C>*R!!Jxi2sYwp^cTUIP(u%h!Uq$nuVc^}G%$!iuoFYM z_X-x_-MsT^bnG`#k%Ot2QQTjYlnr-yG;?m?+CtCtINed9x2w+v?!_`ZibrsCo)6~z z1~y>>eu^;HgJ)yfL|TPWU%`(o_cqDK3Qqm)aqy?B2p8If;{8!(6C&!lQs>JaYC2hVDW zdg)|k?A)NZks6*e9)o72K3>Esyjgg^wD7ziTk%m0>TRR1%EYBHa2W64ZajzAQ@33i z_~{6Ue7OKuWz3a%wrqqbjs(c3N6`Bnju{I`?!fPWTF_CQz{FTX6JP-}_S+{{^(@%}A<=}Vfcqsq?uHs9<>4#N^`iL!NVQdo^2 zOFKEU1Kk7BxeZt0$W9_v%I6^jWv71_HzykP76XT|5FcPSKEz+qKU{okI4je+IagB0 z=6tgwHnSyksa|yW=3bm^;^18GPD5_z{BX;-nfl$_ZHo8DY4|ou2lmqIYH2mU;n)sz zKNX!@6Z!tJl2Jtu{Gy0>D@d-(oa@`HDy<;dkog~X$O}f4>39PFq$Sj|_}7Y-QOn}L zBOmQ4p0#T;^P{VdJ)vY>_rTfsWrT)0PrbvkJhpBMYa5EK~-b zH(oZ~8zfh0EP3E&JfG_o9nh_dV_eCyUnuB*?^srnDqU9>iob8EokkGc*M^ZV2+*1> z&Q;g9jZzvN8Mp@D#E_}DJ%;Tm(6}+gbtUc!i~giY=TMA%2vcx*`sUiPGV0dd+>~Ox zxZrf%9F6RqTg%8qZ9l7R=jsB5SKzf`+gVcjde-D6c(x#^JrlLpXr s*>6-!-5mUo=#J~>(WFeuq>Li}0VW_ZsxWb>`v3p{07*qoM6N<$f+DgT&j0`b literal 820 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 diff --git a/Loop/DerivedAssetsBase.xcassets/settings.imageset/settings@3x.png b/Loop/DerivedAssetsBase.xcassets/settings.imageset/settings@3x.png index 6ee011f3c9edd0661bf7a62fcd29632d4b68d909..4cea84a02790bbbf97f7fe47250fc38437981be2 100644 GIT binary patch literal 5461 zcmaKwRag@apvAZ0=+Rw*f|Q^esg!h!N;gWEAl+`DbP6&UNJ}>&qepjl4^Rdo-IKWg z``!2ZaN_a2o$vcay?Ut%A!Q*2000nmHKjNIrQiRGnD9S;W+Ofc0MN6kE6MBmE$)5u zPqEPt?vp#v_r5s&D}rI-`~)O?$_b+iPgI6z0hp7mUKm7i&_-%{NISWcaCUG#-m5aW zZ%icMxp4eCqvE1dMp{-2ubk`yuvw?>Qs8h}gigp9l2^+sz) zte82(0cZ|@1CR{|hX0(2FvCE{2~`mJa52mSD>g|QKc2%iJ6HiTEjKWp$YAFp41R-X zhF73C2cA-n=bSq$g$hpfQDa}2$M6(e&S+?b&TzEJ()kHhD`l_fELvIg2G)tHnO`Ib z0+9#0AW=lPh3_AG?c}!_2X259#0*BEm3QRQjMb0!@FZYma_mR9Osk4RNmUe{S|23* zKBPy8$iV6IFOp|4(~$=d_i}^|f*v!V!nE!iOHc$FI=w?xwho`u(-SjVS(cok(%HVk zJqd+CTMoa2DbQfa5n>?$qYg3xgeuLlDUaI6gn8nBqS~RNRKuY(SaPftRsb7-ZSczW z8#g%0A(ogulh4Jb65jA)SWnv~Vp|!p0Bo5ZxchmW% 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 literal 2921 zcmb7`i9ZvJ1I9OJ?pz~`q*8h%a^>0xb8VI-jBLq${$#xJnlmhoN{6E`$8uzja+F(M zi7<0b&Utfgh8#J_5U@{;-w}Q01y(jHaEQ)31nyE`!XF*b>AO_ z-aLH;nizlH+t2!4?TriPT8zF{q(N%Q7yXC(&yRVheAbtb3%v68;+MsTP?IzeHuUEQ8!-7 z7qa4!HB;X)N=;JooM>)c@yFDGdzhFrQ_6@%-@UR*_3EQiH>oV+1!VWSWPOik+Sz}U zfRyW=>L`=Ix1z-5Dm_!>k*w@bfp)0>K9h`wrh*Y-CZ+a0~Q)kw5; zVy1MyUZRGcL6rl98z<~%d|zbT)L0mDMPkkvp}RcIn71cBbM6qp`f!-?gj1^tB zipnTuo+tSoS;M``S7`+rqTw?a&rn=*%?(d@JEDLu;A4*ZhmwOj=QR8vA!^mht5Yet zv5Vt5&{;NvsgP}RTjq2SB&}qwPI}V9P}Zos2i;P5@MyUb__h6r0VHirfRM%VERIQ| z3%&GV?X1A+Kv{0Bt`}H9YAR~u9I4nZnMr@hn>%_eySW9Gkp*sAE$LH?Gvmpm-tz_Y zw4?atL`LuvY3i)662PBkRojs82fzR132h;dqt>I#o)7@@iASSUAlFO`s7>M{M(RtR zeXPp1U&Z*vSJKGr`nJ1EpDfQr4HGJjhCAMqh@7lGN=Qz?+Im6`FbOK}YS%xwo$bz# z-Z$Ox^1Qv8w$F6D?TR}goOoFc1N&)HJ+orO9SiW~hj;nTWX#>2Njij0h!X-^`Ik|f z2xAVH7AnqHf6usDYZeK6<*N{(#65cxi$nvZ6*>)#)A6EFYoge?Bv5Ri*o~|4sf4ta zd!1W)B|KWMGnrP)0ayQ|Y96u6FXK!ooWjPBB~@)d`V5Et<-yv#?z{cSpLWB$8fGcHj4xf=x0>9%cbDVL z#OOvFmWx>unHA)^Mo^I1q`lJ*1FWv;dN<){1e2^pdoV{30Xw|^T%>f|PC%3`7KnG= z`&r-s>J?b=#`0H}zDCj2VD!GSSDoAll-7MW7}D@MYIt2y5& z2E1vLr&}~lRlEPm1>cC(gKu5QT6l|^s1MK!TnL+>;oel2H2J9xpFS6GT}a&DMh{-2 zVly0rt}A&vv}XGc?YT^XYdgl2HZn^gxS@(uOk+cauWp->M3zn?GjZd&f`^hf0xKq_ zYSXD)s5sp=JBLkNdZWDOso{^MYYC4Yw&2utSU!f)&tT75(n}Qps~qkM%(;7@K`|+~ zC_Z}|fu?A>Le8U#qKei$O!g|7-ZlY5i=p(+G02Sxn`F{ht&_@U>X#=WdC0ku%b&nqNw>!0z#!40kJDe+)rDJ1)? z%T(F3+CZMa)Rv^)j=m(QZ&2^<<03PgtzM;EfXEdR8B5S?>pKn)O{a^~fViwplTd zy9OHl>x`#8Pq>xP78#n~*pT0~ZjOLuLYBU0yr}}Ml$oWVl;Y~A@1K6%T8FKoKG}+T zN#CM|GqkVp&C{?3s)=7a1WCV&>s~m~A!ax%$j`_pA-tV+q2Ml5y%E61?nyHb&hD&7 z*iRfO@|f29?@Khs0xoqNVXrxJ*C&+MCp3$;PGlMFkmt4-p(j15yJnP4n?X0ygL&FV z9N$td(jT<=G!KK|&#C$KIwrLnVdX%$BlBZ9ZX&ce_ae(pnrpr69vYQBq#c-g{*KkI zrahXtBOrs9aIgBKDm<7kQmt@M+HakLlH1d&AT*i8QgBZIK}LBHGtfdT^8C@{S`6B%*DL2>KpZ@b?VRerqZ(mOa;$!5Yv|zHmqd`&J*HkcMnq V<9i|e_wTz5u(m*$SDIlH{tqu6lS2Ri diff --git a/Loop/DerivedAssetsBase.xcassets/settings.imageset/settings_compact@2x.png b/Loop/DerivedAssetsBase.xcassets/settings.imageset/settings_compact@2x.png deleted file mode 100644 index ccac27347e3adb0f03004325d66cbf2e0935d92f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1424 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 WOMqbt4i{q!Qv)tlRabvEMlJvXaN+d; delta 1377 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 B@l5~# delta 2091 zcmaJ?3se(l7H+6HV8k`l1BHk%DvuyaW+pG9F)B%TCc4wk!ZTIY)Ig`xyfB*gd``vr) z>~VYPcErw}0f2y7nn2=m0a_rUibLZ8CI*QN{lvxkoZMDb0o(ek(ci)zf695WW|~TSF7`$U%g;^N3Kg3Qea?sOsH)!- zeYtv|p-JzU_)N84Ws|W&Ap7}jxkI!%EAV1&nTFHWG0+{vqdyrVA2&c13zZQ^BTF)Q z?$_j-Vtx0-^chDZO$YxOMU~AxkxLEQBA&PX2rF-pBTd?0wf;o@h{A8v0>#;$`k$r( zANh`o;m;m6q`hc}(uxAul&+2KZu!2XJwF*pzeM5c2oXCWOPmq3--RIPp|))>Kc!J-vlWMX_JkRr|10AhUr_r zA@+9U;gzwYd8y0X zZu9g_;u|Hglh5bv;%4_*Wx1dQwO!+#6U+!bHH0QDFR<$xoLD|=X+=Hu@NwXmCB~5NUSc1=XaQp1xicxX*>A&{%g;nxm%C*x$+iJ=R|b1!_uT6B<8Qrg zdqBVAaENU{ks|-_lkd1EyW0$=Px!fxTFgs*I?lVP*t{)au zYB*KSyL)|Sg?c=XVIQhV9u!gSbCPTm(razcH+fRr9h0Cfp`-)@U@aB&{_qeOS*<9imlUA(ra$+^t6oz_LT6#b5_dY$l zlF0Rc2!DRfovx?}KGL4O{QIrG4KJ4sb1h5nM~NFPjNNNHjt`($f4S+~&mBwAmSu&X z_Nb802^y{I@Z8t3Zp0>}y8XM3S55F5dK(c&;NFG$^O~rZg?C!B`w&z_N0%lV&sy= zxgw?06~RQz$o<+&{XdEqER#JrmSTO-0$5Reov<=*P3>IQLO56cZ%W}1P_oxXW%^Wb zF6SUiw#(VwGO=rLw&nHJ)>OB+=^w7W(1+^3%GEjMg`b!FYqCz*&6@T3b=QO|V}(n# zZ7H?hJ-XHfEprOG+$wu*U)FZo5q6%ib34=%cZa;!BfQkKs6DZD$4I(&Z06seJQ9cR zdYN13qc@b27>`m|9&!U{PJ%IKnsz?nV%X& zg#vVZN&z$xqE+iOGDHio-U58J3dPp706y6;LJ%TXN_^DYfM^f{=pX=d#3UYXMh4$C zGyg&~YFQY90@0Y1z!wl3RugSq*yb=i9zYXrL;b=~35o#tu=LYHfH%H|1v6BU7aDSi z6vHdB_1t$;6vE|1hI-<{ngODm#4*3)N-zt8XVmfFIEE<(@cBLxEg}avWKkF zc3bxL+yITQ)?v9r0ByZeuEh?Dxr8Mt20kPNL54;$K%g7?$P4ulzK#n}gCsH_I1GpY zAkYhfyjVb}PNG4#16ZPF$F`v!Gl~#yt$CAR1q?zgkORUH6XY<#C=hhV?hnj&MBER| zJh@JWXaKV&W;|bm=q0F9trFmtnSC!(0uX})zP|t#lSyX+3g8{a zV6d@-@C#7QU=R#rPJfIsV7i%S=(Aa*hcWga)*(9lr#K9VgH_|>I1mHEO8iHR^*{Eq zu|E2k$DrdWgP{KRhh@;&e-dFZu;RYojA|sxL_}l$9)~HnW5zMCCReMmqC@6d(*jfq zHP!*<<_-{GJ-`L!FiQ@C-W(~&ma`$L1fok38IvQINm()<1eWqh|DS_X-ZqgI#k#^= Q3I>zGCb_x_gMCT=1-M}$CjbBd diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 6d88d0f34..a9d99f04f 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -294,27 +294,17 @@ final class StatusTableViewController: LoopChartsTableViewController { return button } - /// Sizing for the icons in the bottom toolbar. + /// Layout of the items in the bottom toolbar. /// - /// On iOS 26 the toolbar items share one Liquid Glass background, and any - /// space item between them splits that background into separate pills. So - /// the gaps here are transparent padding baked into each rendered icon, - /// which widens the spacing while leaving the shared pill intact. + /// The icon assets are all drawn on a 40x40pt canvas with their artwork 30pt tall, so + /// nothing here sizes them. The one thing left to do is pad each item out to the 44pt + /// minimum tap target, with horizontal room between items: on iOS 26 the items share one + /// Liquid Glass background and a space item between them would split it, so the gap is + /// transparent padding on the image itself. /// /// `fileprivate` so the UIImage helper at the bottom of this file can read it. fileprivate enum ToolbarLayout { - /// Height every icon's *visible artwork* is scaled to, in points. The - /// stock assets pad their canvases by wildly different amounts, so - /// matching canvases (the obvious approach) leaves the glyphs uneven. - static let inkHeight: CGFloat = 30 - /// Transparent space kept to each side of the artwork; this is what - /// separates the icons within the shared pill. - static let iconHorizontalPadding: CGFloat = 10 - /// Transparent space kept above and below the artwork in each item. - /// Sized so each item clears the 44pt minimum tap target: the rendered - /// canvas is `inkHeight` plus this margin top and bottom. Horizontally - /// the padding above already puts every item past 44pt. - static let iconVerticalMargin: CGFloat = 7 + static let itemSize = CGSize(width: 60, height: 44) } /// Position of the pre-meal item within `toolbarItems`, recorded at setup @@ -2119,78 +2109,15 @@ private extension UIButton { } private extension UIImage { - /// Bounds of the visible artwork within the image, in points, ignoring any - /// transparent margin around it. - /// - /// The stock assets pad themselves by wildly different amounts -- bolus has - /// essentially none, carbs has ~3.7pt top and bottom, and settings is a 25x25 - /// gear centered on a 25x40 canvas -- so sizing on the canvas leaves the - /// glyphs visibly uneven. Measuring here rather than carrying a table of - /// constants keeps this correct if an asset is ever redrawn. - func inkBounds() -> CGRect? { - guard let cgImage = cgImage, cgImage.width > 0, cgImage.height > 0 else { return nil } - - let width = cgImage.width - let height = cgImage.height - var pixels = [UInt8](repeating: 0, count: width * height * 4) - guard let context = CGContext(data: &pixels, - width: width, - height: height, - bitsPerComponent: 8, - bytesPerRow: width * 4, - space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) - else { return nil } - context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) - - var minX = width, maxX = -1, minY = height, maxY = -1 - for y in 0.. 0 { - minX = min(minX, x) - maxX = max(maxX, x) - minY = min(minY, y) - maxY = max(maxY, y) - } - } - guard maxX >= minX, maxY >= minY else { return nil } - - // CGContext draws bottom-up; flip the vertical range into UIKit's coordinates. - let scaleX = size.width / CGFloat(width) - let scaleY = size.height / CGFloat(height) - return CGRect(x: CGFloat(minX) * scaleX, - y: CGFloat(height - 1 - maxY) * scaleY, - width: CGFloat(maxX - minX + 1) * scaleX, - height: CGFloat(maxY - minY + 1) * scaleY) - } - - /// Scales the image so its visible artwork is `inkHeight` tall, then centers - /// that artwork on a canvas sized to it plus uniform padding. Normalizing on - /// ink rather than canvas is what makes the five icons look the same size. - /// - /// The toolbar icons are vector PDFs with `preserves-vector-representation`, - /// so scaling up stays crisp. Transparent canvas that falls outside the - /// output is simply clipped. + /// Centers the icon on a transparent canvas of the toolbar item size. See `ToolbarLayout`. func toolbarIcon() -> UIImage { - let layout = StatusTableViewController.ToolbarLayout.self - let ink = inkBounds() ?? CGRect(origin: .zero, size: size) - guard ink.width > 0, ink.height > 0 else { return self } - - let scale = layout.inkHeight / ink.height - let drawSize = CGSize(width: size.width * scale, height: size.height * scale) - let scaledInk = ink.applying(CGAffineTransform(scaleX: scale, y: scale)) - let canvas = CGSize(width: scaledInk.width + (layout.iconHorizontalPadding * 2), - height: layout.inkHeight + (layout.iconVerticalMargin * 2)) - - // Offset the whole image so that its ink, not its canvas, ends up centered. - let origin = CGPoint(x: (canvas.width - scaledInk.width) / 2 - scaledInk.minX, - y: (canvas.height - scaledInk.height) / 2 - scaledInk.minY) - + let canvas = StatusTableViewController.ToolbarLayout.itemSize + let origin = CGPoint(x: (canvas.width - size.width) / 2, y: (canvas.height - size.height) / 2) let format = UIGraphicsImageRendererFormat.preferred() format.opaque = false - let rendered = UIGraphicsImageRenderer(size: canvas, format: format).image { _ in - draw(in: CGRect(origin: origin, size: drawSize)) - } - return rendered.withRenderingMode(.alwaysTemplate) + return UIGraphicsImageRenderer(size: canvas, format: format).image { _ in + draw(in: CGRect(origin: origin, size: size)) + }.withRenderingMode(.alwaysTemplate) } }