Key changes in UIKit
Hello! My name is Lena, I work as an iOS developer in KTS. Recently a new version of iOS 18 was released, and I decided to study all the innovations in detail to understand what new opportunities it offers developers. In this article I will tell you and show you the most interesting updates in UIKit – the new TabBar, animations, UIKit/SwiftUI compatibility and much more.
Table of contents
Major changes
One of the key innovations in iOS 18 is an updated interface for launching applications that work with documents. Now, setting up the controller to view files has become simpler and more concise, making the code easier to understand and maintain.
Comparison of code in iOS 17 and iOS 18
Let's look at how the controller was configured for working with documents in the previous version of iOS 17:
class DocumentViewController: UIDocumentViewController { ... }
let documentViewController = DocumentViewController()
let browserViewController = UIDocumentBrowserViewController(
forOpening: [.plainText]
)
window.rootViewController = browserViewController
browserViewController.delegate = self
// MARK: UIDocumentBrowserViewControllerDelegate
func documentBrowser(
_ browser: UIDocumentBrowserViewController,
didPickDocumentsAt documentURLs: [URL]
) {
guard let url = documentURLs.first else { return }
documentViewController.document = StoryDocument(fileURL: url)
browser.present(documentViewController, animated: true)
}
In iOS 17, to customize the file viewing interface, you needed to create two controllers: UIDocumentBrowserViewController
for working with files and custom DocumentViewController
to display the selected document. It was also necessary to implement a delegate UIDocumentBrowserViewControllerDelegate
to handle file selection.
In the new version of iOS 18, developers have the opportunity to significantly simplify the code:
class DocumentViewController: UIDocumentViewController { ... }
let documentViewController = DocumentViewController()
window.rootViewController = documentViewController
Now one controller is enough – DocumentViewController
which is immediately assigned to the window's root controller. This innovation allows you to stop using UIDocumentBrowserViewController
and delegate methods for opening files, making the code more compact and readable.
In iOS 18, developers have new opportunities to customize the look of the document controller. Thanks to the new feature launchOptions
Now you can easily customize the appearance of the interface by changing the background and adding foreground accessories.
Using property launchOptions
You can customize the background and add foreground elements to customize the interface. Let's look at a code example that demonstrates these capabilities:
class DocumentViewController: UIDocumentViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Update the background
launchOptions.background.image = UIImage(resource: .fileBackground)
// Add foreground accessories
launchOptions.foregroundAccessoryView = ForegroundAccessoryView()
}
}
New property launchOptions
significantly simplifies the process of customizing the interface.
New TabBar in iPadOS 18
With the release of iPadOS 18, there is a new convenient API for creating a tab bar (TabBar), which greatly simplifies navigation and interface management. The tab bar is now located at the top of the screen, making navigation easier. Tabs are located closer together, which improves interaction and makes it easier to navigate between sections.
In addition, the new interface provides flexibility in customizing the panel view – you can easily switch between tabBar и sideBar
adapting the design to the application requirements.
To add tabs, developers can use a new approach that allows you to set an array of tabs (UITab) to the tabs property of the controller.
Example code
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let favoritesSection = UITabGroup(
title: "Favorites",
image: UIImage(systemName: "heart.circle"),
identifier: "Section Favorites",
children:
[
UITab(title: "My Books",
image: UIImage(systemName: "heart.circle"),
identifier: "My Books Tab") { _ in
UIViewController()
},
UITab(title: "My Films",
image: UIImage(systemName: "heart.circle"),
identifier: "My Films Tab") { _ in
UIViewController()
},
UITab(title: "My Music",
image: UIImage(systemName: "heart.circle"),
identifier: "My Music Tab") { _ in
UIViewController()
}
]) { _ in
UIViewController()
}
self.tabs = [
UITab(title: "Books",
image: UIImage(systemName: "book"),
identifier: "Books Tab") { _ in
UIViewController()
},
UITab(title: "Films",
image: UIImage(systemName: "film.stack"),
identifier: "Films Tab") { _ in
UIViewController()
},
UITab(title: "Music",
image: UIImage(systemName: "music.quarternote.3"),
identifier: "Music Tab") { _ in
UIViewController()
},
favoritesSection,
UISearchTab { _ in
UINavigationController(
rootViewController: UISearchController()
)
}
]
}
}
A separate section has also been added here UITabGroup
with tabs. When adding at least one tab group (UITabGroup) automatically enables sidebar support (sideBar). The user has the opportunity to change the view himself, and the sideBar automatically appears in landscape orientation:
To create tabs, just set the UITab array to the tabs property of the controller, adding both regular tabs and tab groups (UITabGroup).
Regardless of the tab structure, in iPadOS 18 you can control the appearance of the panel through properties:
// Enable the sidebar.
self.mode = .tabSidebar
// Get the sidebar.
let sidebar = self.sidebar
// Show the sidebar.
self.sidebar.isHidden = false
The new API can be adapted to run on Mac Catalyst and VisionOS, making it universal across platforms.
Fluid transitions
iOS 18 introduces a new interactive transition with zoom. This transition allows you to grab and drag the view both at the start and during the transition, providing a more flexible and fluid user experience.
SwiftUI/UIKit interoperability
iOS 18 improves compatibility between SwiftUI and UIKit, making it easier to use them together in apps. Developers can now more easily integrate SwiftUI elements into UIKit and vice versa, making the code more flexible and reusable.
Let's look at updates in two areas: animation and gesture recognizers.
Animations
In iOS 18, you can now use SwiftUI animation types to animate UIViews, giving you access to the full range of SwiftUI animations, including CustomAnimations
. This allows you to create smooth and expressive animations with less effort.
Examples of use:
SwiftUI
withAnimation(.spring(duration: 0.5)) {
beads.append(Bead())
}
UIKit with SwiftUI Animations
UIView.animate(.spring(duration: 0.5)) {
bead.center = endOfBracelet
}
It's also easier to create smooth, gesture-driven animations using SwiftUI spring animations. Details can be found in the video Enhance your UI animations and transitions.
You can also use SwiftUI Animations to create gesture-driven animations in UIKit:
switch gesture.state {
case .changed:
UIView.animate(.interactiveSpring) {
bead.center = gesture.translation
}
case .ended:
UIView.animate(.spring) {
bead.center = endOfBracelet
}
}
Gesture recognizers
In iOS 18, gesture recognizers in SwiftUI and UIKit were unified, which allows you to set dependencies between gestures of different frameworks. This makes it easier to manage interactions when the SwiftUI view is embedded in UIKit and vice versa.
For example, you can do this so that UITapGestureRecognize
r in UIKit depended on completion TapGesture
in SwiftUIpreventing gesture conflict. This unification allows you to create more flexible and responsive interfaces.
By default, in iOS 18, gestures are recognized simultaneously, and both handlers can be called when double-clicking.
If you give a name to a SwiftUI gesture, for example, "SwiftUIDoubleTap"
this can be used in a delegate UIGestureRecognizerDelegate
to control gesture priority. In this case, the UIKit gesture (tapGesture) will only be executed if the SwiftUI gesture is named "SwiftUIDoubleTap"
ended with an error.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf other: UIGestureRecognizer
) -> Bool {
return other.name == "SwiftUIDoubleTap"
}
}
struct GestureView: View {
let doubleTap = TapGesture(count: 2)
var body: some View {
Circle()
.gesture(doubleTap, name: "SwiftUIDoubleTap")
}
}
General UIKit improvements
Automatic trait tracking
iOS 18 UIKit adds support for automatic tracking traitCollection
in UIView and UIViewController update methods of views such as layoutSubviews
And drawRect
. When the system calls one of these methods, it automatically records which features (traits) you access inside a method.
If one of the monitored properties changes, UIKit automatically calls the appropriate method to update it, e.g. setNeedsLayout или setNeedsDisplay
to ensure the interface is updated correctly.
In iOS 17 to update the interface when changing UITraitHorizontalSizeClass
it was necessary to manually track changes and call setNeedsLayout
. This required additional checking in the code to determine if changes had occurred in horizontalSizeClass
and update the layout accordingly.
This is what the code would look like on iOS 17, where you need to track a sign orizontalSizeClass
to change the layout accordingly:
Code for iOS 17
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
registerForTraitChanges(
[UITraitHorizontalSizeClass.self],
action: #selector(UIView.setNeedsLayout)
)
}
override func layoutSubviews() {
super.layoutSubviews()
if traitCollection.horizontalSizeClass == .compact {
setCompactLayout()
} else {
setRegularLayout()
}
}
}
With the advent of automatic feature tracking (automatic trait tracking) in iOS 18, the need to manually log changes is gone. Now, when the layoutSubviews method is called, the system automatically logs the use of the feature horizontalSizeClass
. If this attribute changes, UIKit itself calls the appropriate update method, for example, setNeedsLayout.
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
if traitCollection.horizontalSizeClass == .compact {
setCompactLayout()
} else {
setRegularLayout()
}
}
}
List environment trait
iOS 18 updates the Collections and Table View APIs, making it easier to update cells. All views in list sections UICollectionView и UITableView
now have the property traitCollection.listEnvironment
which describes the style of the list.
With introduction listEnvironment
in iOS 18, describing the list style, objects UIListContentConfiguration и UIBackgroundConfiguration
began to use this new feature.
When cells are updated to a new state, they automatically adjust their properties according to listEnvironment
. This innovation eliminates the need for developers to manually manage list style when customizing a cell.
Now, when configuring a cell, you only need to specify the desired style for the content: regular cell, header, or footer. This simplifies the process of customizing cells, allowing the system to automatically handle the design depending on the selected style.
For example, consider the Browse tab in the Files application.
This example uses UICollectionView
with a compositional layout to display a list of locations, favorites and tags in two different styles, depending on the context:
Portrait orientation: In this case, the list is displayed using the style
insetGrouped
which creates a neat and organized presentation;Landscape Orientation: When you change the orientation, the same list is presented in the sidebar of the split view controller, providing easier access to information.
The Files app also uses the sidebar appearance when customizing its compositional layout, which improves the user experience and allows the user to interact with content more effectively.
Consider a function from the Files app in iOS 17 that generates content and background configurations for location cells in the watch list:
func configurations(for location: FileLocation,
listAppearance: UICollectionLayoutListConfiguration.Appearance) ->
(UIListContentConfiguration, UIBackgroundConfiguration) {
let isSideBar = listAppearance == .sidebar
var contentConfiguration: UIListContentConfiguration
let backgroundConfiguration: UIBackgroundConfiguration
contentConfiguration = isSideBar ? .sidebarCell() : .cell()
backgroundConfiguration = isSideBar ? .listSidebarCell() : .listGroupedCell()
contentConfiguration.text = location.title
contentConfiguration.image = location.thumbnailImage
return (contentConfiguration, backgroundConfiguration)
}
This approach allows the interface to adapt based on context, improving the user experience.
The function takes a structure as arguments FileLocation
and the appearance of the list (listAppearance). It first checks if the list's appearance matches the sidebar style and stores the result in a local variable isSideBar
.
It then manually selects content and background configurations based on the variable isSidebar
. Because content and background configurations were created manually, cells using these configurations had to be manually reconfigured when the list's appearance changed. This meant that the function had to be called again to update the cells' settings and display them according to the new context. This approach increased the amount of code and required careful management of interface state.
func configurations(for location: FileLocation) ->
(UIListContentConfiguration, UIBackgroundConfiguration) {
var contentConfiguration = UIListContentConfiguration.cell()
let backgroundConfiguration = UIBackgroundConfiguration.listCell()
contentConfiguration.text = location.title
contentConfiguration.image = location.thumbnailImage
return (contentConfiguration, backgroundConfiguration)
}
In iOS 18, the cell configuration function can be greatly simplified. The Files app now uses the new Cell Designer to customize content and the Designer listCell
for background configuration.
When configurations are applied to a cell, they are automatically updated based on the state of the cell's configuration. Thanks to the new listEnvironment
these configurations now synchronize their properties with the list style. For UIListContentConfiguration и UIBackgroundConfiguration
existing cell configurations update their appearance based on the list environment attribute. For UIBackgroundConfiguration
three new constructors have been added: listCell, listHeader и listFooter.
UIUpdateLink
UIUpdateLink is a new feature in iOS 18 that makes it easier to implement complex animations that require periodic UI updates. UIUpdateLink is similar to CADisplayLink but offers more features such as:
Automatic view tracking: UIUpdateLink automatically adjusts to the parent view, making it easier to manage animations.
Low Latency Mode: This feature allows you to put the system into a low latency mode, which is especially useful for drawing applications.
Moreover, UIUpdateLink promotes better performance and efficient battery usage with its advanced features.
Example imageView animated up and down using sine function:
class UpdateLinkViewController: UIViewController {
lazy var updateLink = UIUpdateLink(
view: imageView,
actionTarget: self,
selector: #selector(update)
)
override func viewDidLoad() {
super.viewDidLoad()
updateLink.requiresContinuousUpdates = true
updateLink.isEnabled = true
}
@objc func update(updateLink: UIUpdateLink,
updateInfo: UIUpdateInfo) {
imageView.center.y = sin(updateInfo.modelTime)
* 100 + view.bounds.midY
}
}
When initializing UIUpdateLink, you need to specify a UIView instance. It is automatically activated when the view becomes visible on the screen, and disabled when the view is hidden.
The update function uses the updateInfo.modelTime parameter to track the time of the last update. Setting the requireContinuousUpdates property to true ensures continuous updates as long as UIUpdateLink
active When set to false, updates only occur when there are external triggers such as gestures or layer changes.
Symbol animations
In iOS 18, SF Symbols in UIKit received new features for character animationsincluding three animation presets:
.wiggle – causes the symbol to wiggle in any direction or at any angle to attract the user's attention;
.breathe – smoothly scales the symbol, creating a “breathing” effect;
.rotate – rotates part of the symbol around the designated anchor point, adding dynamism.
We've also added a new .periodic behavior that allows you to set the number of animation repetitions and adjust the delay between them.
Additionally, the .replace effect now uses “magic replace” by default, smoothly animating icon changes and slash displays for more natural transitions.
To add an effect:
imageView.addSymbolEffect(.rotate)
Replace effect:
let imageView = UIImageView(image: UIImage(systemName: "person.crop.circle.badge.clock"))
imageView.setSymbolImage(UIImage(systemName: "person.crop.circle.badge.checkmark")!, contentTransition: .replace)
Sensory feedback
iPadOS 17.5 improves touch feedback for iPad using the Apple Pencil Pro and Magic Keyboard. UIFeedbackGenerator
now supports new ways to provide haptic feedback and can be tied to a view as an interaction.
Key updates:
Location: When providing feedback, applications now need to specify the coordinates of the action within the view (location) that triggered the feedback.
New generator: Added
UICanvasFeedbackGenerator
ideal for applications with large work surfaces, such as drawing or design applications.
For example, in a drawing application, dragging shapes and snapping them to a grid can use haptic feedback to improve the user experience.
@ViewLoading var feedbackGenerator: UICanvasFeedbackGenerator
override func viewDidLoad() {
super.viewDidLoad()
feedbackGenerator = UICanvasFeedbackGenerator(view: view)
}
func dragAligned(_ sender: UIPanGestureRecognizer) {
feedbackGenerator.alignmentOccurred(at: sender.location(in: view))
}
When creating feedbackGenerator
it needs to be associated with a view (view). When feedback is triggered, the location that initiated the action is transmitted, for example, the coordinates of the gesture recognizer that triggered the alignment of the figure.
Now, if you use the Apple Pencil Pro to drag, haptic feedback is provided directly through the stylus as the shape moves along the snap guide.
Depending on the device and settings, UIFeedbackGenerator
can produce tactile cues, sound effects, or a combination of both, providing a richer sensory experience.
Text improvements
In iOS 18, text formatting options have been expanded: UITextView has added built-in editing toolbar. It allows you to change the font, size and color of text, and add attributes such as lists, making the formatting process easier and more user-friendly.
The format bar in iOS 18 also supports the new text highlight functionallowing you to quickly and easily apply different styles and accents to a selected piece of text.
Highlighting works by applying two new attributes. textHighlightStyle
– for the range of text to be highlighted, and textHighlightColorScheme
— for the color used to visualize the selection.
var attributes = [NSAttributedString.Key: Any]()
// Highlight style
attributes[.textHighlightStyle] = NSAttributedString.TextHighlightStyle.default
// Highlight color scheme
attributes[.textHighlightColorScheme] = NSAttributedString.TextHighlightColorScheme.orange
In addition to the standard hue color scheme, iOS 18 comes with five preset color schemes. These schemes allow you to quickly apply different color styles to text formatting, increasing customization options.
The text formatting bar can be customized by setting UITextFormattingViewController.Configuration
in textView:
Writing Tools support
Writing Tools is a new feature in iOS, iPadOS, and macOS that enhances your text editing experience by offering tools to correct and transform text. The Writing Tools panel appears on top of the keyboard when you select text, and also in the context menu next to the Cut, Copy, and Paste options.
By default, UITextView, NSTextView, and WKWebView support the Writing Tools feature, but to fully experience its capabilities, you must use TextKit 2. This provides a broader set of text editing and processing functions.
For example, using Writing Tools you can convert text into a table:
More details about Writing Tools can be found in the report Get started with Writing Tools.
The new updates we've discussed here can be used in your applications right now.
Our other articles about iOS development: