Repository: instructure/canvas-ios
Pull request #3206: Increase minimum iOS version to 17
Author: login: ndrsszsz
Created: 2025-03-04T18:10:11Z | Merged: 2025-03-20T09:38:17Z

## Increase minimum iOS version to 17

refs: MBL-18643
affects: Student, Teacher, Parent
release note: None
test plan: Smoke test all apps.

## Checklist

- [x] Tested on phone
- [x] Tested on tablet
- [x] Tested on iOS17
- [x] Tested on iOS18



Diff:
diff --git a/Core/Core/Common/CommonUI/CoreWebView/Model/DefaultWKWebViewConfiguration.swift b/Core/Core/Common/CommonUI/CoreWebView/Model/DefaultWKWebViewConfiguration.swift
index c35035ba0f..3aee6208f6 100644
--- a/Core/Core/Common/CommonUI/CoreWebView/Model/DefaultWKWebViewConfiguration.swift
+++ b/Core/Core/Common/CommonUI/CoreWebView/Model/DefaultWKWebViewConfiguration.swift
@@ -27,11 +27,7 @@ public extension WKWebViewConfiguration {
     }
 
     func applyDefaultSettings() {
-        if #available(iOS 17, *) {
-            // iOS 16 has issues with the webview's content size
-            // after exiting fullscreen mode so we allow only iOS 17
-            preferences.isElementFullscreenEnabled = true
-        }
+        preferences.isElementFullscreenEnabled = true
         allowsInlineMediaPlayback = true
         allowsPictureInPictureMediaPlayback = true
         allowsAirPlayForMediaPlayback = true
diff --git a/Core/Core/Common/CommonUI/CoreWebView/View/CoreWebView.swift b/Core/Core/Common/CommonUI/CoreWebView/View/CoreWebView.swift
index 6ea526af5b..10a4aec364 100644
--- a/Core/Core/Common/CommonUI/CoreWebView/View/CoreWebView.swift
+++ b/Core/Core/Common/CommonUI/CoreWebView/View/CoreWebView.swift
@@ -137,10 +137,7 @@ open class CoreWebView: WKWebView {
         isOpaque = false
         backgroundColor = UIColor.clear
         translatesAutoresizingMaskIntoConstraints = false
-
-        if #available(iOS 16.4, *) {
-            isInspectable = true
-        }
+        isInspectable = true
 
         addScript(js)
         handle("resize") { [weak self] message in
diff --git a/Core/Core/Common/CommonUI/InstUI/Views/Badge.swift b/Core/Core/Common/CommonUI/InstUI/Views/Badge.swift
index be8fca6cc1..cf8058087d 100644
--- a/Core/Core/Common/CommonUI/InstUI/Views/Badge.swift
+++ b/Core/Core/Common/CommonUI/InstUI/Views/Badge.swift
@@ -74,7 +74,6 @@ extension View {
     }
 }
 
-@available(iOS 17.0, *)
 #Preview {
     @Previewable @State var badgeValueIndex: Int = 0
     let badgeValues: [Int?] = [nil, 1, 99, 100]
diff --git a/Core/Core/Common/CommonUI/InstUI/Views/Cells/SelectionMenuCell.swift b/Core/Core/Common/CommonUI/InstUI/Views/Cells/SelectionMenuCell.swift
index cd71919c3d..999f172fe6 100644
--- a/Core/Core/Common/CommonUI/InstUI/Views/Cells/SelectionMenuCell.swift
+++ b/Core/Core/Common/CommonUI/InstUI/Views/Cells/SelectionMenuCell.swift
@@ -122,7 +122,6 @@ extension InstUI.SelectionMenuCell {
 
 #if DEBUG
 
-@available(iOS 17.0, *)
 #Preview {
     @Previewable @State var selection: String?
 
diff --git a/Core/Core/Common/CommonUI/InstUI/Views/Toggle.swift b/Core/Core/Common/CommonUI/InstUI/Views/Toggle.swift
index 0f450a84a2..c7a7515b42 100644
--- a/Core/Core/Common/CommonUI/InstUI/Views/Toggle.swift
+++ b/Core/Core/Common/CommonUI/InstUI/Views/Toggle.swift
@@ -40,10 +40,10 @@ extension InstUI {
                 toggle
             }
             .accessibilityElement(children: .combine)
-            .accessibilityAddTraitsIsToggle()
+            .accessibilityAddTraits(.isToggle)
             .accessibilityRemoveTraits(.isStaticText)
             .accessibilityValue(accessibilityValue)
-            .addHapticFeedback(isOn: isOn)
+            .sensoryFeedback(.impact, trigger: isOn)
         }
 
         private var toggle: some View {
@@ -89,7 +89,7 @@ extension InstUI {
                     .animation(.none, value: isOn)
             }
             .padding(.horizontal, 1.4)
-            .compatibleGeometryGroup()
+            .geometryGroup()
         }
 
         private var backgroundColor: Color {
@@ -130,36 +130,8 @@ extension InstUI {
     }
 }
 
-private extension View {
-
-    @available(iOSApplicationExtension,
-               obsoleted: 17.0,
-               message: "Use `accessibilityAddTraits(.isToggle)` directly.")
-    @ViewBuilder
-    func accessibilityAddTraitsIsToggle() -> some View {
-        if #available(iOSApplicationExtension 17.0, *) {
-            self.accessibilityAddTraits(.isToggle)
-        } else {
-            self
-        }
-    }
-
-    @available(iOSApplicationExtension,
-               obsoleted: 17.0,
-               message: "Use `sensoryFeedback(.selection, trigger: isOn)` directly.")
-    @ViewBuilder
-    func addHapticFeedback(isOn: Bool) -> some View {
-        if #available(iOSApplicationExtension 17.0, *) {
-            self.sensoryFeedback(.impact, trigger: isOn)
-        } else {
-            self
-        }
-    }
-}
-
 #if DEBUG
 
-@available(iOS 17.0, *)
 #Preview(traits: .sizeThatFitsLayout) {
     @Previewable @State var isOn1 = true
     @Previewable @State var isOn2 = false
diff --git a/Core/Core/Common/CommonUI/SwiftUIViews/RemoteImage.swift b/Core/Core/Common/CommonUI/SwiftUIViews/RemoteImage.swift
index 5da956251d..17a310e132 100644
--- a/Core/Core/Common/CommonUI/SwiftUIViews/RemoteImage.swift
+++ b/Core/Core/Common/CommonUI/SwiftUIViews/RemoteImage.swift
@@ -60,7 +60,7 @@ public struct RemoteImage: View {
                 }
             }
         }
-        .compatibleGeometryGroup()
+        .geometryGroup()
         .frame(width: width, height: height)
     }
 
diff --git a/Core/Core/Common/CommonUI/UIViews/CoreSwitch.swift b/Core/Core/Common/CommonUI/UIViews/CoreSwitch.swift
index eade1a21d4..34c483a7b0 100644
--- a/Core/Core/Common/CommonUI/UIViews/CoreSwitch.swift
+++ b/Core/Core/Common/CommonUI/UIViews/CoreSwitch.swift
@@ -127,7 +127,6 @@ private struct ToggleWrapper: View {
     }
 }
 
-@available(iOS 17.0, *)
 #Preview(traits: .sizeThatFitsLayout) {
     let createSwitchView: () -> CoreSwitch = {
         let result = CoreSwitch(frame: .zero)
diff --git a/Core/Core/Common/CommonUI/UIViews/SwitchTableViewCell.swift b/Core/Core/Common/CommonUI/UIViews/SwitchTableViewCell.swift
index 34d19c4cfd..eaa09f28db 100644
--- a/Core/Core/Common/CommonUI/UIViews/SwitchTableViewCell.swift
+++ b/Core/Core/Common/CommonUI/UIViews/SwitchTableViewCell.swift
@@ -54,7 +54,6 @@ public class SwitchTableViewCell: UITableViewCell {
     }
 }
 
-@available(iOS 17.0, *)
 #Preview {
     let cell = SwitchTableViewCell(style: .default, reuseIdentifier: nil)
     cell.textLabel?.text = "test"
diff --git a/Core/Core/Common/CommonUI/ViewModifiers/CompatibleGeometryGroup.swift b/Core/Core/Common/CommonUI/ViewModifiers/CompatibleGeometryGroup.swift
deleted file mode 100644
index 58585b453c..0000000000
--- a/Core/Core/Common/CommonUI/ViewModifiers/CompatibleGeometryGroup.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-// This file is part of Canvas.
-// Copyright (C) 2024-present  Instructure, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
-//
-
-import SwiftUI
-
-public extension View {
-
-    @available(iOSApplicationExtension,
-               obsoleted: 17.0,
-               message: "Use geometryGroup() directly.")
-    @ViewBuilder
-    func compatibleGeometryGroup() -> some View {
-        if #available(iOS 17.0, *) {
-            self.geometryGroup()
-        } else {
-            self
-        }
-    }
-}
diff --git a/Core/Core/Features/Planner/CalendarMain/CalendarViewController.swift b/Core/Core/Features/Planner/CalendarMain/CalendarViewController.swift
index b5df7485a8..ab16586795 100644
--- a/Core/Core/Features/Planner/CalendarMain/CalendarViewController.swift
+++ b/Core/Core/Features/Planner/CalendarMain/CalendarViewController.swift
@@ -233,12 +233,10 @@ class CalendarViewController: ScreenViewTrackableViewController {
 
         // Manually trigger a calendar height update upon rotation
         if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
-            if #available(iOS 17.0, *) {
-                // On iOS 17 embedded VC traits need to be updated first, otherwise the size values from
-                // the embedded VC will be outdated in `updateExpanded()`.
-                // This would be also needed if we used `registerForTraitChanges()`, unfortunately.
-                updateTraitsIfNeeded()
-            }
+            // On iOS 17 embedded VC traits need to be updated first, otherwise the size values from
+            // the embedded VC will be outdated in `updateExpanded()`.
+            // This would be also needed if we used `registerForTraitChanges()`, unfortunately.
+            updateTraitsIfNeeded()
             updateExpanded()
         }
     }
diff --git a/Core/Core/Features/Planner/CalendarMain/View/Calendar/CalendarUtils.swift b/Core/Core/Features/Planner/CalendarMain/View/Calendar/CalendarUtils.swift
index fbabde3e54..a36957fc5a 100644
--- a/Core/Core/Features/Planner/CalendarMain/View/Calendar/CalendarUtils.swift
+++ b/Core/Core/Features/Planner/CalendarMain/View/Calendar/CalendarUtils.swift
@@ -47,13 +47,7 @@ func withAnimation<Result>(
     _ body: () throws -> Result,
     completion: @escaping () -> Void
 ) rethrows -> Result {
-    if #available(iOS 17.0, *) {
-        return try withAnimation(.spring(duration: duration), body, completion: completion)
-    } else {
-        let result = try withAnimation(.spring(duration: duration), body)
-        DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: completion)
-        return result
-    }
+    return try withAnimation(.spring(duration: duration), body, completion: completion)
 }
 
 extension TimeInterval {
diff --git a/Core/project.yml b/Core/project.yml
index 00e27067db..77facd13dc 100644
--- a/Core/project.yml
+++ b/Core/project.yml
@@ -7,7 +7,7 @@ options:
   groupSortPosition: top
   bundleIdPrefix: com.instructure.icanvas
   deploymentTarget:
-    iOS: 16.0
+    iOS: 17.0
   createIntermediateGroups: true
 settings:
   CODE_SIGN_STYLE: Automatic
@@ -100,4 +100,4 @@ schemes:
       targets: [CoreTests, CoreTester, TestsFoundation]
       testPlans:
         - path: ../TestPlans/CoreTests.xctestplan
-          defaultPlan: true
\ No newline at end of file
+          defaultPlan: true
diff --git a/Parent/Parent.xcodeproj/project.pbxproj b/Parent/Parent.xcodeproj/project.pbxproj
index 19f3c4aa32..0d59fcef88 100644
--- a/Parent/Parent.xcodeproj/project.pbxproj
+++ b/Parent/Parent.xcodeproj/project.pbxproj
@@ -1480,7 +1480,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 = 17.0;
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -1533,7 +1533,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 = 17.0;
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
diff --git a/Student/Student/Assignments/AssignmentDetails/Reminders/View/AssignmentRemindersView.swift b/Student/Student/Assignments/AssignmentDetails/Reminders/View/AssignmentRemindersView.swift
index b3d9fb2564..3ec25bd3f1 100644
--- a/Student/Student/Assignments/AssignmentDetails/Reminders/View/AssignmentRemindersView.swift
+++ b/Student/Student/Assignments/AssignmentDetails/Reminders/View/AssignmentRemindersView.swift
@@ -48,7 +48,7 @@ public struct AssignmentRemindersView: View {
             .confirmationAlert(isPresented: $viewModel.showingDeleteConfirmDialog,
                                presenting: viewModel.confirmAlert)
             .animation(.default, value: viewModel.reminders)
-            .compatibleGeometryGroup()
+            .geometryGroup()
             .onAppear {
                 viewController.value.view.backgroundColor = .backgroundLightest
             }
@@ -63,7 +63,7 @@ public struct AssignmentRemindersView: View {
                                            viewModel.reminderDeleteDidTap(reminderModel)
                                        })
             .transition(.asymmetric(insertion: .opacity, removal: .move(edge: .trailing)))
-            .compatibleGeometryGroup()
+            .geometryGroup()
         }
     }
 
@@ -107,7 +107,7 @@ public struct AssignmentRemindersView: View {
         .accessibilityAction {
             viewModel.newReminderDidTap(view: viewController.value)
         }
-        .compatibleGeometryGroup()
+        .geometryGroup()
     }
 
     private var divider: some View {
diff --git a/Student/Widgets/AnnouncementsWidget/View/AnnouncementWidgetView.swift b/Student/Widgets/AnnouncementsWidget/View/AnnouncementWidgetView.swift
index 3f9137eb11..7dc2fddfa5 100644
--- a/Student/Widgets/AnnouncementsWidget/View/AnnouncementWidgetView.swift
+++ b/Student/Widgets/AnnouncementsWidget/View/AnnouncementWidgetView.swift
@@ -25,7 +25,9 @@ struct AnnouncementsWidgetView: View {
 
     var body: some View {
         buildView()
-            .compatibleContainerBackground(Color.backgroundLightest)
+            .containerBackground(for: .widget) {
+                Color.backgroundLightest
+            }
     }
 
     @ViewBuilder
diff --git a/Student/Widgets/AnnouncementsWidget/View/MediumLarge/MediumLargeAnnouncementsView.swift b/Student/Widgets/AnnouncementsWidget/View/MediumLarge/MediumLargeAnnouncementsView.swift
index 699fa758bd..be42906f7a 100644
--- a/Student/Widgets/AnnouncementsWidget/View/MediumLarge/MediumLargeAnnouncementsView.swift
+++ b/Student/Widgets/AnnouncementsWidget/View/MediumLarge/MediumLargeAnnouncementsView.swift
@@ -50,7 +50,6 @@ struct MediumLargeAnnouncementsView: View {
                 }
             }.padding(.top, 40) // This is to move the first entry below the header
         }
-        .compatibleContentMargins()
     }
 }
 
diff --git a/Student/Widgets/AnnouncementsWidget/View/Small/SmallAnnouncementsView.swift b/Student/Widgets/AnnouncementsWidget/View/Small/SmallAnnouncementsView.swift
index d40581f77f..4e3377a96a 100644
--- a/Student/Widgets/AnnouncementsWidget/View/Small/SmallAnnouncementsView.swift
+++ b/Student/Widgets/AnnouncementsWidget/View/Small/SmallAnnouncementsView.swift
@@ -34,7 +34,6 @@ struct SmallAnnouncementsView: View {
             Spacer(minLength: 0)
         }
         .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading)
-        .compatibleContentMargins()
         .widgetURL(announcement.url)
     }
 
diff --git a/Student/Widgets/Common/View/CompatibleContainerBackground.swift b/Student/Widgets/Common/View/CompatibleContainerBackground.swift
deleted file mode 100644
index 2c6a604d59..0000000000
--- a/Student/Widgets/Common/View/CompatibleContainerBackground.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// This file is part of Canvas.
-// Copyright (C) 2023-present  Instructure, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
-//
-
-import SwiftUI
-
-extension View {
-
-    @available(iOSApplicationExtension,
-               obsoleted: 17.0,
-               message: "Use containerBackground directly.")
-    func compatibleContainerBackground(_ backgroundView: some View = SwiftUI.EmptyView()) -> some View {
-        if #available(iOSApplicationExtension 17.0, *) {
-            return containerBackground(for: .widget) {
-                backgroundView
-            }
-        } else {
-            return background {
-                backgroundView
-            }
-        }
-    }
-}
diff --git a/Student/Widgets/Common/View/CompatibleContentMargins.swift b/Student/Widgets/Common/View/CompatibleContentMargins.swift
deleted file mode 100644
index b8f1434db3..0000000000
--- a/Student/Widgets/Common/View/CompatibleContentMargins.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-// This file is part of Canvas.
-// Copyright (C) 2023-present  Instructure, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
-//
-
-import SwiftUI
-
-extension View {
-
-    /**
-     On iOS 17 there's a default content margin added by the system to the widget based on
-     the environment it's shown so in this case we don't need to add any extra paddings. Below
-     iOS 17 we add a default `padding()`.
-     */
-    @available(iOSApplicationExtension,
-               obsoleted: 17.0,
-               message: "Use the system provided content margin (delete this method and don't add anything in its place.).")
-    func compatibleContentMargins() -> some View {
-        if #available(iOSApplicationExtension 17.0, *) {
-            return self
-        } else {
-            return padding()
-        }
-    }
-}
diff --git a/Student/Widgets/Common/View/EmptyView.swift b/Student/Widgets/Common/View/EmptyView.swift
index 9aab831e18..9a8c702304 100644
--- a/Student/Widgets/Common/View/EmptyView.swift
+++ b/Student/Widgets/Common/View/EmptyView.swift
@@ -40,8 +40,9 @@ struct EmptyView: View {
                 .multilineTextAlignment(.center)
                 .frame(maxWidth: .infinity, maxHeight: .infinity)
         }
-        .compatibleContentMargins()
-        .compatibleContainerBackground()
+        .containerBackground(for: .widget) {
+            SwiftUI.EmptyView()
+        }
     }
 
     private let title: Text
diff --git a/Student/Widgets/GradesWidget/View/GradesWidgetView.swift b/Student/Widgets/GradesWidget/View/GradesWidgetView.swift
index fedbd9398b..3bbbda791f 100644
--- a/Student/Widgets/GradesWidget/View/GradesWidgetView.swift
+++ b/Student/Widgets/GradesWidget/View/GradesWidgetView.swift
@@ -32,7 +32,9 @@ struct GradesWidgetView: View {
 
     var body: some View {
         buildView()
-            .compatibleContainerBackground(Color.backgroundLightest)
+            .containerBackground(for: .widget) {
+                Color.backgroundLightest
+            }
     }
 
     @ViewBuilder
diff --git a/Student/Widgets/GradesWidget/View/MediumLarge/MediumLargeGradesView.swift b/Student/Widgets/GradesWidget/View/MediumLarge/MediumLargeGradesView.swift
index 12e513e522..a49c827855 100644
--- a/Student/Widgets/GradesWidget/View/MediumLarge/MediumLargeGradesView.swift
+++ b/Student/Widgets/GradesWidget/View/MediumLarge/MediumLargeGradesView.swift
@@ -56,7 +56,6 @@ struct MediumLargeGradesView: View {
                 Spacer()
             }.padding(.top, 4) // This is to vertically center the first header with the logo
         }
-        .compatibleContentMargins()
     }
 
     private let model: GradeModel
@@ -73,10 +72,14 @@ struct MediumLargeGradesView: View {
 struct MediumLargeGradesViewPreviews: PreviewProvider {
     static var previews: some View {
         MediumLargeGradesView(model: .make(), lineCount: 2)
-            .compatibleContainerBackground()
+            .containerBackground(for: .widget) {
+                SwiftUI.EmptyView()
+            }
             .previewContext(WidgetPreviewContext(family: .systemMedium))
         MediumLargeGradesView(model: .make(), lineCount: 5)
-            .compatibleContainerBackground()
+            .containerBackground(for: .widget) {
+                SwiftUI.EmptyView()
+            }
             .previewContext(WidgetPreviewContext(family: .systemLarge))
     }
 }
diff --git a/Student/Widgets/GradesWidget/View/Small/SmallGradeView.swift b/Student/Widgets/GradesWidget/View/Small/SmallGradeView.swift
index 1bf829b5c1..3ba13157f7 100644
--- a/Student/Widgets/GradesWidget/View/Small/SmallGradeView.swift
+++ b/Student/Widgets/GradesWidget/View/Small/SmallGradeView.swift
@@ -37,7 +37,6 @@ struct SmallGradeView: View {
                 .minimumScaleFactor(0.5)
             Spacer(minLength: 0)
         }
-        .compatibleContentMargins()
         .widgetURL(gradeItem.route)
     }
 
@@ -55,7 +54,9 @@ struct SmallGradeViewPreviews: PreviewProvider {
         SmallGradeView(gradeItem: GradeItem(name: "Earth: The Pale Blue Dot on two lines or more since it's very long",
                                             grade: "95.50 / 100",
                                             color: .textDanger))
-        .compatibleContainerBackground()
+        .containerBackground(for: .widget) {
+            SwiftUI.EmptyView()
+        }
         .previewContext(WidgetPreviewContext(family: .systemSmall))
     }
 }
diff --git a/Student/project-ci.yml b/Student/project-ci.yml
index 42c603ff3b..6eb9a0a15c 100644
--- a/Student/project-ci.yml
+++ b/Student/project-ci.yml
@@ -7,7 +7,7 @@ options:
   groupSortPosition: top
   bundleIdPrefix: com.instructure.icanvas
   deploymentTarget:
-    iOS: 16.0
+    iOS: 17.0
   createIntermediateGroups: true
 settings:
   CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED: true
diff --git a/Student/project.yml b/Student/project.yml
index 21be689691..70307da1a4 100644
--- a/Student/project.yml
+++ b/Student/project.yml
@@ -7,7 +7,7 @@ options:
   groupSortPosition: top
   bundleIdPrefix: com.instructure.icanvas
   deploymentTarget:
-    iOS: 16.0
+    iOS: 17.0
   createIntermediateGroups: true
 settings:
   CODE_SIGN_STYLE: Automatic ## This line is removed in the project-ci.yml file so Bitrise can manage code sign. 
diff --git a/Teacher/Teacher.xcodeproj/project.pbxproj b/Teacher/Teacher.xcodeproj/project.pbxproj
index 54a14d3fa6..e80bc173a2 100644
--- a/Teacher/Teacher.xcodeproj/project.pbxproj
+++ b/Teacher/Teacher.xcodeproj/project.pbxproj
@@ -1501,7 +1501,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 = 17.0;
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -1552,7 +1552,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 = 17.0;
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;


Reviews:
user: login: rh12
body: Some preliminary findings:
- The minimum version should be `17.0`, not `17.6`
- Student app and Core should be also updated in it's `project.yml` (and also in the `ci` version)
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T07:04:12Z
user: login: suhaibabsi-inst
body: Code +1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T12:25:07Z
user: login: suhaibabsi-inst
body: QA +1, iPhone XS, iOS 18.3.0
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T12:41:59Z
user: login: vargaat
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T09:35:24Z
user: login: ndrsszsz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T09:49:10Z
user: login: vargaat
body: QA+1
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-19T14:47:29Z

Review comments:
diff_hunk: @@ -40,9 +40,9 @@ extension InstUI {
                 toggle
             }
             .accessibilityElement(children: .combine)
-            .accessibilityAddTraitsIsToggle()
+            .accessibilityAddTraits(.isToggle)
             .accessibilityValue(accessibilityValue)
-            .addHapticFeedback(isOn: isOn)
+            .sensoryFeedback(.selection, trigger: isOn)
path: Core/Core/Common/CommonUI/InstUI/Views/Toggle.swift
user: login: vargaat
body: Previously this was `.impact` instead of `.selection` which (based on my experiments) better resembled the native switch's feedback. Why did you change it?
created_at: 2025-03-17T09:21:02Z
author_association: COLLABORATOR
diff_hunk: @@ -234,12 +234,7 @@ class CalendarViewController: ScreenViewTrackableViewController {
 
         // Manually trigger a calendar height update upon rotation
         if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
-            if #available(iOS 17.0, *) {
-                // On iOS 17 embedded VC traits need to be updated first, otherwise the size values from
-                // the embedded VC will be outdated in `updateExpanded()`.
-                // This would be also needed if we used `registerForTraitChanges()`, unfortunately.
path: Core/Core/Features/Planner/CalendarMain/CalendarViewController.swift
user: login: vargaat
body: I would leave this explanatory comment in case someone comes around and needs to modify something related to this.
created_at: 2025-03-17T09:22:23Z
author_association: COLLABORATOR
diff_hunk: @@ -40,9 +40,9 @@ extension InstUI {
                 toggle
             }
             .accessibilityElement(children: .combine)
-            .accessibilityAddTraitsIsToggle()
+            .accessibilityAddTraits(.isToggle)
             .accessibilityValue(accessibilityValue)
-            .addHapticFeedback(isOn: isOn)
+            .sensoryFeedback(.selection, trigger: isOn)
path: Core/Core/Common/CommonUI/InstUI/Views/Toggle.swift
user: login: ndrsszsz
body: Thanks for noticing, I think it was accidental, I changed it back to `.impact` just now.
created_at: 2025-03-17T09:49:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: inst-danger
created_at: 2025-03-04T18:14:16Z
body: 
<!--
  0 failure: 
  1 warning:  This pull request...
  
  3 markdown notices
  DangerID: danger-id-Danger;
-->


<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Warnings</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:warning:</td>
      <td>This pull request will not generate a release note.</td>
    </tr>
  </tbody>
</table>


#### Affected Apps: Student, Teacher, Parent

[MBL-18643](https://instructure.atlassian.net/browse/MBL-18643)

Coverage | New % | Master % | Delta
-------- | ----- | -------- | -----
**Canvas iOS** | 91.61% | 91.61% | 0%
<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against 06d0ec8768a66a580e1df92145d64d3456a37992
</p>


[MBL-18643]: https://instructure.atlassian.net/browse/MBL-18643?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
author_association: CONTRIBUTOR
user: login: inst-danger
created_at: 2025-03-05T09:19:02Z
body: <details><summary>Teacher Build QR Code:</summary>
<img src="https://api.qrserver.com/v1/create-qr-code/?data=https%3A%2F%2Fapp.bitrise.io%2Fapp%2F88a3641e29e4745c%2Finstallable-artifacts%2F48ca9403dd20d37d%2Fpublic-install-page%2F5c97b87a0c2a3584db62932297bb56bd&size=150x150" />
</details>


<!-- TeacherBuild -->
author_association: CONTRIBUTOR
user: login: inst-danger
created_at: 2025-03-05T09:19:17Z
body: <details><summary>Parent Build QR Code:</summary>
<img src="https://api.qrserver.com/v1/create-qr-code/?data=https%3A%2F%2Fapp.bitrise.io%2Fapp%2F88a3641e29e4745c%2Finstallable-artifacts%2F4cca2856a013a588%2Fpublic-install-page%2Fbc7f618593d2d7a770f27eae8510db0c&size=150x150" />
</details>


<!-- ParentBuild -->
author_association: CONTRIBUTOR
user: login: inst-danger
created_at: 2025-03-05T15:16:24Z
body: <details><summary>Student Build QR Code:</summary>
<img src="https://api.qrserver.com/v1/create-qr-code/?data=https%3A%2F%2Fapp.bitrise.io%2Fapp%2F88a3641e29e4745c%2Finstallable-artifacts%2F6f7fdd5235d6c513%2Fpublic-install-page%2F877abca8a5dc87bc01a2c740935f2aa8&size=150x150" />
</details>


<!-- StudentBuild -->
author_association: CONTRIBUTOR