From f8801ae58b372337e5ed57758ea5f1ee9a550a59 Mon Sep 17 00:00:00 2001 From: Danis Ziganshin Date: Sun, 7 Jun 2026 18:00:33 +0300 Subject: [PATCH 1/2] Add migration ticket: bump deployment target to iOS 18 A prompt that fires an agent to plan and implement raising the minimum deployment target to iOS 18 across all targets and removing now-dead backwards-compatibility code. Co-Authored-By: Claude Opus 4.8 --- migrations/04-ios18-deployment-target.md | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 migrations/04-ios18-deployment-target.md diff --git a/migrations/04-ios18-deployment-target.md b/migrations/04-ios18-deployment-target.md new file mode 100644 index 0000000..7d9805e --- /dev/null +++ b/migrations/04-ios18-deployment-target.md @@ -0,0 +1,42 @@ +# Migration: Bump deployment target to iOS 18 + +> **Agent prompt — paste this to start the task.** +> +> You are raising GrowingUp's minimum deployment target to **iOS 18** across all +> targets and modernizing the source/build settings that the bump unlocks or +> requires. First produce a concrete plan (post it for review), then implement +> it. +> +> **Current state** +> - `IPHONEOS_DEPLOYMENT_TARGET = 12.0`, `objectVersion = 50`, Swift 5.0. +> - Targets: `GrowingUp` (app), `Core` (framework), `Widget` (Today extension), +> `GrowingUpTests`. +> - Legacy source idioms exist, e.g. `protocol X: class` (`TodayView`, +> `TodayProviding`), `#available` / availability guards for old iOS versions, +> and a custom `Core/iOS Framework APIs/Obfuscator.swift`. +> +> **Goal** +> - Set the deployment target to **iOS 18.0** on every target consistently. +> - Remove now-dead backwards-compatibility code paths and modernize idioms the +> new floor makes safe. +> +> **Plan must cover** +> 1. Setting `IPHONEOS_DEPLOYMENT_TARGET = 18.0` on app, Core, Widget and test +> targets; bumping `objectVersion`/`LastUpgradeCheck` and Swift language +> version to current; removing stale per-config overrides. +> 2. An audit of every `#available` / `if #available` / `@available` guard and +> deletion of branches now unreachable below iOS 18. +> 3. Source modernization: `class`→`AnyObject` in protocol inheritance, remove +> workarounds for pre-18 behavior, audit the dead `Obfuscator`. +> 4. **Device-support callout:** iOS 18 drops every device older than the +> iPhone XS/XR generation. Confirm this is intended and state it in the PR. +> 5. Coordination with the WidgetKit ticket (needs ≥14) and the SwiftUI ticket +> (uses `NavigationStack`/`@Observable`, ≥16) — iOS 18 satisfies both; note +> merge ordering. **Overlaps PR #4 sub-task B (which proposed iOS 16); this +> ticket supersedes that floor with iOS 18.** +> +> **Acceptance criteria** +> - All targets build and tests pass on the current Xcode/SDK with an iOS 18 +> minimum and no deprecated-setting warnings. +> - No remaining availability guards for iOS < 18. +> - `Readme.md` "Requirements" updated to iOS 18.0+. From f92452dc19d4581c80f4785c86c79d11c7ebdf96 Mon Sep 17 00:00:00 2001 From: Danis Ziganshin Date: Fri, 12 Jun 2026 19:47:22 +0300 Subject: [PATCH 2/2] Bump deployment target to iOS 18 and modernize unlocked idioms - IPHONEOS_DEPLOYMENT_TARGET = 18.0 on app, Core, Widget and test targets; objectVersion 50 -> 56, LastUpgradeCheck -> 2600 (Xcode 26) - Remove availability guards now dead under the iOS 18 floor: iOS 11 placeholder fallback (PersonOverview), iOS 12 userInterfaceStyle guard (UIColor+Extensions), iOS 17 containerBackground shim (Widget) - Replace deprecated topLayoutGuide with safeAreaLayoutGuide - protocol X: class -> AnyObject across all protocols - Delete dead Obfuscator.swift plus its commented-out call sites and obfuscated constants in Logging/Constants - Readme requirements: iOS 18.0+ SWIFT_VERSION stays 5.0: Swift 6 language mode hinges on the concurrency direction discussed in PR #8, so it is deferred there. Co-Authored-By: Claude Fable 5 --- Core/Constants.swift | 10 --- Core/Logging.swift | 15 ---- Core/iOS Framework APIs/Obfuscator.swift | 80 ------------------- GrowingUp.xcodeproj/project.pbxproj | 24 +++--- .../WDImageCropViewController.swift | 2 +- .../WDImagePicker/WDImagePicker.swift | 2 +- GrowingUp/Others/UIColor+Extensions.swift | 8 +- .../DateTableViewCell/DateCellPresenter.swift | 2 +- .../ImagesCellPresenter.swift | 2 +- .../ImagesTableViewCell.swift | 4 +- .../TextFieldCellPresenter.swift | 2 +- .../TextFieldTableViewCell.swift | 4 +- .../ToggleCellPresenter.swift | 2 +- .../ToggleTableViewCell.swift | 2 +- .../EditPerson/Edit/EditPersonPresenter.swift | 2 +- .../EditPerson/Views/DatePickerView.swift | 2 +- .../EditPerson/Views/RemoveButtonFooter.swift | 2 +- .../PersonOverviewViewController.swift | 8 +- .../PersonsListViewController.swift | 2 +- Readme.md | 2 +- Widget/GrowingUpWidget.swift | 15 +--- 21 files changed, 31 insertions(+), 161 deletions(-) delete mode 100644 Core/iOS Framework APIs/Obfuscator.swift diff --git a/Core/Constants.swift b/Core/Constants.swift index 8e9359a..357ada0 100644 --- a/Core/Constants.swift +++ b/Core/Constants.swift @@ -14,14 +14,4 @@ public enum Constants { public static let widgetBundle = "pro.ziganshin.GrowingUp.Widget" public static let widgetPersonIndexKey = "personIndex" public static let openPersonNotification = Notification.Name(rawValue: "pro.ziganshin.GrowingUp.openPersonWithIndex") - -// static let swiftyBearAppId = "bJPXa3" -// static let swiftyBearAppSecret = "q0fxlcfjfoajqbjpw2wlrwfc6gvunh5k" -// static let swiftyBearEncryption = "rteZzjbjHucLtxjeiaicf8nfcLkEra6e" -// static let mixpanelToken = "cf19da2125b5c9625a5de1f21696f45b" - - public static let obfusSBAppId: [UInt8] = [54, 43, 35, 51, 44, 82] - public static let obfusSBAppSecret: [UInt8] = [37, 81, 21, 19, 33, 2, 8, 11, 1, 10, 19, 36, 34, 45, 8, 26, 18, 81, 3, 56, 19, 4, 13, 46, 87, 9, 23, 18, 11, 26, 123, 56] - public static let obfusSBEncryption: [UInt8] = [38, 21, 22, 49, 55, 11, 12, 11, 47, 16, 17, 2, 39, 55, 8, 15, 12, 2, 29, 55, 7, 75, 5, 43, 2, 34, 10, 34, 23, 19, 120, 54] - public static let obfusMPToken: [UInt8] = [55, 7, 66, 82, 41, 0, 92, 80, 85, 80, 16, 123, 48, 118, 84, 88, 80, 2, 65, 48, 4, 66, 13, 127, 80, 88, 88, 81, 3, 70, 123, 49] } diff --git a/Core/Logging.swift b/Core/Logging.swift index 7cf903b..fbbb7e1 100644 --- a/Core/Logging.swift +++ b/Core/Logging.swift @@ -11,21 +11,6 @@ import Foundation public enum Logging { public static func setup() { -// let console = ConsoleDestination() -// let file = FileDestination() -// let obfuscator = Obfuscator() -// let appId = obfuscator.reveal(key: Constants.obfusSBAppId) -// let appSecretId = obfuscator.reveal(key: Constants.obfusSBAppSecret) -// let encryption = obfuscator.reveal(key: Constants.obfusSBEncryption) -// let platform = SBPlatformDestination(appID: appId, appSecret: appSecretId, encryptionKey: encryption) -// #if DEBUG -// file.logFileURL = URL(fileURLWithPath: "/tmp/swiftybeaver.log") -// #endif -// SwiftyBeaver.addDestination(console) -// SwiftyBeaver.addDestination(file) -// SwiftyBeaver.addDestination(platform) -// -// Mixpanel.sharedInstance(withToken: obfuscator.reveal(key: Constants.obfusMPToken)) } public static func logError(_ error: CoreError) { diff --git a/Core/iOS Framework APIs/Obfuscator.swift b/Core/iOS Framework APIs/Obfuscator.swift deleted file mode 100644 index b8a2cbf..0000000 --- a/Core/iOS Framework APIs/Obfuscator.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// Obfuscator.swift -// Core -// -// Created by zigdanis on 26/04/2019. -// Copyright © 2019 zigdanis. All rights reserved. -// - -import Foundation - -class Obfuscator { - - // MARK: - Variables - - /// The salt used to obfuscate and reveal the string. - private var salt: String - - // MARK: - Initialization - - init() { - self.salt = "\(String(describing: TaskManager.self))\(String(describing: NSObject.self))" - } - - init(with salt: String) { - self.salt = salt - } - - // MARK: - Instance Methods - - /** - This method obfuscates the string passed in using the salt - that was used when the Obfuscator was initialized. - - - parameter string: the string to obfuscate - - - returns: the obfuscated string in a byte array - */ - func bytesByObfuscatingString(string: String) -> [UInt8] { - let text = [UInt8](string.utf8) - let cipher = [UInt8](self.salt.utf8) - let length = cipher.count - - var encrypted = [UInt8]() - - for textChunk in text.enumerated() { - encrypted.append(textChunk.element ^ cipher[textChunk.offset % length]) - } - - #if DEVELOPMENT - print("Salt used: \(self.salt)\n") - print("Swift Code:\n************") - print("// Original \"\(string)\"") - print("let key: [UInt8] = \(encrypted)\n") - #endif - - return encrypted - } - - /** - This method reveals the original string from the obfuscated - byte array passed in. The salt must be the same as the one - used to encrypt it in the first place. - - - parameter key: the byte array to reveal - - - returns: the original string - */ - func reveal(key: [UInt8]) -> String { - let cipher = [UInt8](self.salt.utf8) - let length = cipher.count - - var decrypted = [UInt8]() - - for nextKey in key.enumerated() { - decrypted.append(nextKey.element ^ cipher[nextKey.offset % length]) - } - - return String(bytes: decrypted, encoding: .utf8)! - } -} diff --git a/GrowingUp.xcodeproj/project.pbxproj b/GrowingUp.xcodeproj/project.pbxproj index 724fd35..bc0484f 100644 --- a/GrowingUp.xcodeproj/project.pbxproj +++ b/GrowingUp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -67,7 +67,6 @@ 096E61ED226667F6000181D4 /* AddPersonConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096E61EC226667F6000181D4 /* AddPersonConfigurator.swift */; }; 096E61EF22666D46000181D4 /* AddPersonPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096E61EE22666D46000181D4 /* AddPersonPresenter.swift */; }; 0978FDE7227256ED00487BC2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0978FDE9227256ED00487BC2 /* Localizable.strings */; }; - 097E1AEE22739175003D4FB4 /* Obfuscator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097E1AED22739175003D4FB4 /* Obfuscator.swift */; }; 097E1AF82274D620003D4FB4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 097E1AF62274D620003D4FB4 /* Localizable.strings */; }; 098054F6223C296600E24D40 /* AddPersonPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098054F5223C296600E24D40 /* AddPersonPresenterTests.swift */; }; 098054F9223C2A7900E24D40 /* EditPersonViewSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098054F8223C2A7900E24D40 /* EditPersonViewSpy.swift */; }; @@ -265,7 +264,6 @@ 096E61F022666DC9000181D4 /* EditPersonUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPersonUseCase.swift; sourceTree = ""; }; 0978FDE8227256ED00487BC2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 0978FDEA227256FF00487BC2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - 097E1AED22739175003D4FB4 /* Obfuscator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Obfuscator.swift; sourceTree = ""; }; 097E1AF72274D620003D4FB4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 097E1AF92274D630003D4FB4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 098054F5223C296600E24D40 /* AddPersonPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPersonPresenterTests.swift; sourceTree = ""; }; @@ -577,7 +575,6 @@ isa = PBXGroup; children = ( 092AA35D2257C79D002EAEE8 /* TaskManager.swift */, - 097E1AED22739175003D4FB4 /* Obfuscator.swift */, ); path = "iOS Framework APIs"; sourceTree = ""; @@ -1011,7 +1008,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1010; - LastUpgradeCheck = 1010; + LastUpgradeCheck = 2600; ORGANIZATIONNAME = zigdanis; TargetAttributes = { 09051B3A21C558DB008A99C6 = { @@ -1201,7 +1198,6 @@ 09F6B52623535E26003A1A01 /* MemoryCache.swift in Sources */, 09E75944227735E9005025AD /* UUID+Extensions.swift in Sources */, 0983E764226D9652007EC8D2 /* AddPersonUseCase.swift in Sources */, - 097E1AEE22739175003D4FB4 /* Obfuscator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1386,7 +1382,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = XMSU8WJG5R; INFOPLIST_FILE = Widget/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1409,7 +1405,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = XMSU8WJG5R; INFOPLIST_FILE = Widget/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1442,7 +1438,7 @@ GCC_DYNAMIC_NO_PIC = NO; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_NO_PIE = NO; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1479,7 +1475,7 @@ GCC_DYNAMIC_NO_PIC = NO; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_NO_PIE = NO; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1592,7 +1588,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1648,7 +1644,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1669,7 +1665,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = XMSU8WJG5R; INFOPLIST_FILE = "GrowingUp/Supporting Files//Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1693,7 +1689,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = XMSU8WJG5R; INFOPLIST_FILE = "GrowingUp/Supporting Files//Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/GrowingUp/3rd Party Libraries/WDImagePicker/WDImageCropViewController.swift b/GrowingUp/3rd Party Libraries/WDImagePicker/WDImageCropViewController.swift index 6671bfd..1e4992b 100755 --- a/GrowingUp/3rd Party Libraries/WDImagePicker/WDImageCropViewController.swift +++ b/GrowingUp/3rd Party Libraries/WDImagePicker/WDImageCropViewController.swift @@ -8,7 +8,7 @@ import UIKit -protocol WDImageCropControllerDelegate: class { +protocol WDImageCropControllerDelegate: AnyObject { func imageCropController(_ imageCropController: WDImageCropViewController, didFinishWithCroppedImage croppedImage: UIImage) func imageCropControllerFailedCroppingImage(_ imageCropController: WDImageCropViewController) } diff --git a/GrowingUp/3rd Party Libraries/WDImagePicker/WDImagePicker.swift b/GrowingUp/3rd Party Libraries/WDImagePicker/WDImagePicker.swift index b8aa2ef..3dcf560 100755 --- a/GrowingUp/3rd Party Libraries/WDImagePicker/WDImagePicker.swift +++ b/GrowingUp/3rd Party Libraries/WDImagePicker/WDImagePicker.swift @@ -13,7 +13,7 @@ enum CropSize { case circle } -protocol WDImagePickerDelegate: class { +protocol WDImagePickerDelegate: AnyObject { func imagePicker(_ imagePicker: WDImagePicker, pickedImage: UIImage) func imagePickerDidCancel(_ imagePicker: WDImagePicker) } diff --git a/GrowingUp/Others/UIColor+Extensions.swift b/GrowingUp/Others/UIColor+Extensions.swift index e7f713b..dd8bbff 100644 --- a/GrowingUp/Others/UIColor+Extensions.swift +++ b/GrowingUp/Others/UIColor+Extensions.swift @@ -15,11 +15,7 @@ extension UIColor { static let darkBg = UIColor.black static func bgColor(for traitCollection: UITraitCollection) -> UIColor { - if #available(iOS 12.0, *) { - let isLight = traitCollection.userInterfaceStyle == .light - return isLight ? .lightBg : .darkBg - } else { - return .white - } + let isLight = traitCollection.userInterfaceStyle == .light + return isLight ? .lightBg : .darkBg } } diff --git a/GrowingUp/Scenes/EditPerson/Cells/DateTableViewCell/DateCellPresenter.swift b/GrowingUp/Scenes/EditPerson/Cells/DateTableViewCell/DateCellPresenter.swift index 9fe93cf..8825553 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/DateTableViewCell/DateCellPresenter.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/DateTableViewCell/DateCellPresenter.swift @@ -8,7 +8,7 @@ import Foundation -protocol DateCellPresenter: class { +protocol DateCellPresenter: AnyObject { func configure(cell: DateCellView, forRow row: Int) func valueFor(row: Int, didChangeTo value: Date) func valueFor(row: Int) -> Date? diff --git a/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesCellPresenter.swift b/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesCellPresenter.swift index 1f68289..8801abe 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesCellPresenter.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesCellPresenter.swift @@ -9,7 +9,7 @@ import Foundation import Core -protocol ImagesCellPresenter: class { +protocol ImagesCellPresenter: AnyObject { func configure(cell: ImagesCellView, forRow row: Int, with delegate: ImagesCellViewDelegate) func valueFor(row: Int, didChangeTo value: PersonImages) func valueFor(row: Int) -> PersonImages? diff --git a/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift b/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift index 9e664ae..960cee7 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift @@ -9,13 +9,13 @@ import UIKit import Core -protocol ImagesCellView: class { +protocol ImagesCellView: AnyObject { func display(appPic: PersonImage?) func display(widgetPic: PersonImage?) func setup(with delegate: ImagesCellViewDelegate, forRow row: Int) } -protocol ImagesCellViewDelegate: class { +protocol ImagesCellViewDelegate: AnyObject { func showAppPicImagePickerFor(row: Int) func showWidgetPicImagePickerFor(row: Int) } diff --git a/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldCellPresenter.swift b/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldCellPresenter.swift index e3ba45e..8a88d88 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldCellPresenter.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldCellPresenter.swift @@ -8,7 +8,7 @@ import Foundation -protocol TextFieldCellPresenter: class { +protocol TextFieldCellPresenter: AnyObject { func configure(cell: TextFieldCellView, forRow row: Int) func valueFor(row: Int, didChangeTo value: String) func valueFor(row: Int) -> String? diff --git a/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldTableViewCell.swift b/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldTableViewCell.swift index 9e82cc5..73280a1 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldTableViewCell.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldTableViewCell.swift @@ -8,14 +8,14 @@ import UIKit -protocol TextFieldCellView: class { +protocol TextFieldCellView: AnyObject { func display(title: String) func display(value: String) func display(placeholder: String) func setup(with presenter: TextFieldCellPresenter, observer: TextFieldObserver?, forRow row: Int) } -protocol TextFieldObserver: class { +protocol TextFieldObserver: AnyObject { func textDidChange(forView: TextFieldCellView, text: String) } diff --git a/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleCellPresenter.swift b/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleCellPresenter.swift index 944ee34..bdba25b 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleCellPresenter.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleCellPresenter.swift @@ -8,7 +8,7 @@ import Foundation -protocol ToggleCellPresenter: class { +protocol ToggleCellPresenter: AnyObject { func configure(cell: ToggleCellView, forRow row: Int) func valueFor(row: Int, didChangeTo value: Bool) func valueFor(row: Int) -> Bool diff --git a/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleTableViewCell.swift b/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleTableViewCell.swift index b31572f..f7e0d07 100644 --- a/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleTableViewCell.swift +++ b/GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleTableViewCell.swift @@ -14,7 +14,7 @@ protocol ToggleCellView { func setup(with delegate: ToggleCellDelegate?, forRow row: Int) } -protocol ToggleCellDelegate: class { +protocol ToggleCellDelegate: AnyObject { func toggle(toggle: ToggleCellView, didChangeStateForRow row: Int, to state: Bool) } diff --git a/GrowingUp/Scenes/EditPerson/Edit/EditPersonPresenter.swift b/GrowingUp/Scenes/EditPerson/Edit/EditPersonPresenter.swift index 7c24cca..5cc80c7 100644 --- a/GrowingUp/Scenes/EditPerson/Edit/EditPersonPresenter.swift +++ b/GrowingUp/Scenes/EditPerson/Edit/EditPersonPresenter.swift @@ -28,7 +28,7 @@ protocol EditPersonPresenter: TextFieldObserver, ToggleCellDelegate { func shouldShowRemoveButton() -> Bool } -protocol EditPersonPresenterDelegate: class { +protocol EditPersonPresenterDelegate: AnyObject { func editPersonPresenter(_ presenter: EditPersonPresenter, didAdd person: Person) func editPersonPresenter(_ presenter: EditPersonPresenter, didEdit person: Person) func editPersonPresenter(_ presenter: EditPersonPresenter, didRemove person: Person) diff --git a/GrowingUp/Scenes/EditPerson/Views/DatePickerView.swift b/GrowingUp/Scenes/EditPerson/Views/DatePickerView.swift index 662c092..36759ee 100644 --- a/GrowingUp/Scenes/EditPerson/Views/DatePickerView.swift +++ b/GrowingUp/Scenes/EditPerson/Views/DatePickerView.swift @@ -8,7 +8,7 @@ import UIKit -protocol DatePickerViewDelegate: class { +protocol DatePickerViewDelegate: AnyObject { func datePicker(picker: DatePickerView, selectedDate date: Date) func datePickerDidHide(picker: DatePickerView) } diff --git a/GrowingUp/Scenes/EditPerson/Views/RemoveButtonFooter.swift b/GrowingUp/Scenes/EditPerson/Views/RemoveButtonFooter.swift index 9ae635e..edf2105 100644 --- a/GrowingUp/Scenes/EditPerson/Views/RemoveButtonFooter.swift +++ b/GrowingUp/Scenes/EditPerson/Views/RemoveButtonFooter.swift @@ -8,7 +8,7 @@ import UIKit -protocol RemoveButtonDelegate: class { +protocol RemoveButtonDelegate: AnyObject { func removeTouched() } diff --git a/GrowingUp/Scenes/PersonOverview/PersonOverviewViewController.swift b/GrowingUp/Scenes/PersonOverview/PersonOverviewViewController.swift index 846626f..a77da3b 100644 --- a/GrowingUp/Scenes/PersonOverview/PersonOverviewViewController.swift +++ b/GrowingUp/Scenes/PersonOverview/PersonOverviewViewController.swift @@ -49,16 +49,12 @@ final class PersonOverviewViewController: UIViewController, PersonOverviewView { } private func setupPersonNameTopConstraint() { - let top = personName.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor) + let top = personName.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) top.isActive = true } private func setupNoPicPlaceholder() { - if #available(iOS 11.0, *) { - noPicPlaceholder.text = "🤷" - } else { - noPicPlaceholder.text = "¯\\_(ツ)_/¯" - } + noPicPlaceholder.text = "🤷" } private func setupTapGesture() { diff --git a/GrowingUp/Scenes/PersonsList/PersonsListViewController.swift b/GrowingUp/Scenes/PersonsList/PersonsListViewController.swift index 8285df1..a874d5d 100644 --- a/GrowingUp/Scenes/PersonsList/PersonsListViewController.swift +++ b/GrowingUp/Scenes/PersonsList/PersonsListViewController.swift @@ -13,7 +13,7 @@ protocol PageViewControllerViewable: UIViewController { var index: Int { get } } -protocol PersonsListView: class { +protocol PersonsListView: AnyObject { func updateListOfScreens(defaultPage index: Int) } diff --git a/Readme.md b/Readme.md index 118ec40..017db9a 100644 --- a/Readme.md +++ b/Readme.md @@ -52,7 +52,7 @@ no Carthage or CocoaPods step is required: ## Requirements * Xcode 16 or newer (developed against Xcode 26 / iOS 26 SDK). -* iOS 16.0+ deployment target. +* iOS 18.0+ deployment target. ## Building & Running diff --git a/Widget/GrowingUpWidget.swift b/Widget/GrowingUpWidget.swift index edf8e22..973bdbf 100644 --- a/Widget/GrowingUpWidget.swift +++ b/Widget/GrowingUpWidget.swift @@ -14,7 +14,7 @@ struct GrowingUpWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: AgeWidgetProvider()) { entry in AgeWidgetEntryView(entry: entry) - .widgetContainerBackground() + .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("GrowingUp") .description("See your pinned people and their live age.") @@ -28,16 +28,3 @@ struct GrowingUpWidgetBundle: WidgetBundle { GrowingUpWidget() } } - -private extension View { - /// iOS 17+ requires `containerBackground` for home-screen widgets; on iOS 16 - /// the system provides the background, so this is a no-op there. - @ViewBuilder - func widgetContainerBackground() -> some View { - if #available(iOS 17.0, *) { - containerBackground(.fill.tertiary, for: .widget) - } else { - self - } - } -}