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 – DocumentViewControllerwhich 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.

Result

Result

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.

Interface customization

Interface customization

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 и sideBaradapting 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:

UITabGroup with tabs

UITabGroup with tabs

sideBar

sideBar

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 UITapGestureRecognizer in UIKit depended on completion TapGesture in SwiftUIpreventing gesture conflict. This unification allows you to create more flexible and responsive interfaces.

UIKit depends on TapGesture completion in SwiftUI

UIKit depends on TapGesture completion in SwiftUI

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 или setNeedsDisplayto 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 horizontalSizeClassand update the layout accordingly.

This is what the code would look like on iOS 17, where you need to track a sign orizontalSizeClassto 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.listEnvironmentwhich 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.

Browse tab in the Files app

Browse tab in the Files app

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:

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.

Sidebar

Sidebar

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 listEnvironmentthese 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:

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:

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:

Text formatting panel

Text formatting panel

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.

Writing Tools panel

Writing Tools panel

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:

Convert text to table

Convert text to 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:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *