iOS Interview Questions (SwiftUI)

Updated Jun 01, 2023#ios#swift#swiftui#interview

The existence of SwiftUI has somehow rendered UIKit obsolete, you should get busy learning SwiftUI because it is the future of app development on Apple platforms. Preparing for a SwiftUI interview involves a combination of learning the fundamental concepts of SwiftUI and practicing building user interfaces with the framework.



The Apple documentation provides a comprehensive overview of SwiftUI and its various components. Be sure to read through the documentation thoroughly and take notes on important concepts and features.

Since SwiftUI is built on top of Swift, it’s important to have a solid understanding of the Swift programming language. Be sure to review topics such as variables, functions, closures, classes, and structs.

If you’ve built any projects with SwiftUI in the past, be prepared to discuss them in detail during the interview. Be ready to explain the challenges you faced, how you overcame them, and what you learned in the process.

  1. Difference between .task() vs .onAppear()
// iOS 13.0+, macOS 10.15+, watchOS 6.0+
func onAppear(perform action: (() -> Void)? = nil) -> some View

// iOS 15.0+, macOS 12.0+, watchOS 8.0+
func task(priority: TaskPriority = .userInitiated, _ action: @escaping () async -> Void) -> some View
func task<T>(id value: T, priority: TaskPriority = .userInitiated, _ action: @escaping () async -> Void) -> some View where T : Equatable

Both .task() and .onAppear() are view modifiers that can be used to run some code when a view is shown. But there are some differences between them:

  • .task() allows you to use the async/await directly to perform asynchronous work. You have to use Task or DispatchQueue to run async code in .onAppear().
  • .task() will automatically cancel the task when the view is destroyed, if it has not already finished. .onAppear() does not have this feature, so you may need to manually cancel any ongoing work in .onDisappear().
  • .task() can take an id parameter that allows you to cancel and restart the task when the value changes.
  • .task() can also take a priority parameter that allows you to specify the priority of the task relative to other tasks.

In general, .task() is a more powerful and convenient modifier than .onAppear() for running asynchronous work when a view is shown. However, it is only available in iOS 15+, so you may need to use .onAppear() for backward compatibility. You can always use .onAppear() with Task to replicate what .task() does.

➔ Read more

  1. Will SwiftUI replace UIKit?

SwiftUI is absolutely going to be the future of development for Apple platforms, but it will take a long time to gain adoption at the level of UIKit. As each year goes by, SwiftUI grows in strength, adoption, and support, and as SwiftUI grows UIKit will start to shrink.

So, whether SwiftUI will replace UIKit depends on your project requirements, target audience, and personal preference. You can also use both frameworks together in the same project if you need to.

  1. How to use UIKit components in SwiftUI and vice versa?

To use UIKit components in SwiftUI, you can use UIViewRepresentable protocol to wrap a UIKit view and make it compatible with SwiftUI, and UIViewControllerRepresentable protocol to wrap a UIKit view controller.

To use SwiftUI components in UIKit, you can use the UIHostingController class to create a view controller that hosts a SwiftUI view, then present or embed this view controller in your UIKit app.

  1. What is declarative UI programming?

Declarative UI programming is a programming paradigm where the user interface is described in a declarative manner, specifying what the UI should look like rather than explicitly describing how to achieve that appearance.

Instead of imperatively defining step-by-step instructions for building the user interface, developers declare the desired state of the UI, and the underlying framework or library takes care of updating the UI accordingly.

In declarative UI programming, you define the UI structure and behavior using a markup language or a domain-specific language (DSL), which allows you to express the desired UI hierarchy, layout, styles, and interactions. The framework or library then interprets and applies these declarations to create and update the UI automatically.

  1. Difference between @StateObject and @ObservedObject?

A property marked as @StateObject will keep its initially assigned ObservableObject instance as long as the view is needed, even when the struct gets recreated by SwiftUI.

class DataModel: ObservableObject {
  /* ... */
}

struct FooView: View {
  @StateObject private var model = DataModel()
  var body: some View {
    VStack {
      BarView(model: model)
    }
  }
}

struct BarView: View {
  @ObservedObject var model: DataModel
  var body: some View {
    Text("Hello")
  }
}

An @ObservedObject is used to wrap ObservableObject instances that are not created or owned by the view that’s used in. Internally, SwiftUI will not keep an @ObservedObject around when it discards and recreates a view if this is needed for a fresh render.

  1. What are the benefits of using SwiftUI?
  • It has a declarative syntax that makes it easy to write and understand the UI code.
  • It offers a live preview that lets you see the changes in your UI instantly without recompiling.
  • It supports automatic features such as Dynamic Type, Dark Mode, Localization, and Accessibility.
  • It is cross-platform and can be used for macOS, iOS, iPadOS, watchOS, and tvOS.
  • It can be mixed with UIKit using UIHostingController for more flexibility and compatibility.
  1. Why does SwiftUI use structs for views?

Swift provides a number of features that make structs better than classes in performance and thread-safety.

Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple references to the same instance as happens with classes.

With Structs, there is much less need to worry about memory leaks or multiple threads racing to access/modify a single instance of a variable. This is especially important when passing around a variable to many classes and/or in a multithreaded environment. If you can always send a copy of your variable to other places, you never have to worry about that other place changing the value of your variable underneath you.

  1. How does an observable object announce changes?

ObservableObject is a type of object with a publisher that emits before the object has changed. By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.

Using @Published is the easiest way to control state updates, you can also call objectWillChange.send() manually to put out the news that our data has changed so that any subscribed views can refresh.

class Model: ObservableObject {
  @Published var name: String

  var age: Int {
    willSet {
      objectWillChange.send()
    }
  }

  init(name: String, age: Int) {
      self.name = name
      self.age = age
  }
}
  1. Why always declare state as private?

SwiftUI manages the storage of a property that you declare as state. When the value changes, SwiftUI updates the parts of the view hierarchy that depend on the value. Use state as the single source of truth for a given value stored in a view hierarchy.

struct PlayButton: View {
  @State private var isPlaying: Bool = false

  var body: some View {
    Button(isPlaying ? "Pause" : "Play") {
      isPlaying.toggle()
    }
  }
}

Don’t initialize a state property of a view at the point in the view hierarchy where you instantiate the view, because this can conflict with the storage management that SwiftUI provides.

To avoid this, always declare state as private, and place it in the highest view in the view hierarchy that needs access to the value. Then share the state with any child views that also need access, either directly for read-only access, or as a binding for read-write access.

  1. Does the order of SwiftUI modifiers matter?

When a modifier just changes the environment that its target view will be rendered in, then the order often doesn’t matter.

However, if that modifier can only be applied to a specific type of view, then we can only apply it as long as we’re dealing with that kind of view directly.

The order of modifiers that wrap their target view, on the other hand, often matters quite a lot, and a different modifier order can end up yielding a very different result.

  1. What are the different types of views available in SwiftUI?
  • Basic views: These are the simplest and most common views that display text, images, shapes, colors, spacers, dividers, etc. (Text, Image, Circle, Color, Spacer, Divider, etc.)
  • Control views: These are views that provide user interaction and control over some aspect of the app. (Button, Toggle, Slider, Picker, Stepper, TextField, etc.)
  • Container views: These are views that can contain and arrange other views in various ways. (VStack, HStack, ZStack, LazyVStack, LazyHStack, LazyVGrid, LazyHGrid, List, Form, GroupBox, etc.)
  • Navigation views: These are views that provide navigation and presentation options for your app. (NavigationView, TabView, NavigationLink, Popover, Sheet, Alert, ActionSheet, etc.)
  • Drawing and animation views: These are views that provide drawing and animation capabilities for your app. (Path, Shape, GeometryReader, Canvas, DrawingGroup, AnimatableModifier, AnimationViewModifier, etc.)
  • Chart views: These are views that provide charting and data visualization capabilities for your app. Examples of chart views are LineChartViewStyle (Swift Charts), BarChartViewStyle (Swift Charts), PieChartViewStyle (Swift Charts), etc.
  • WidgetKit views: These are views that provide widget-specific functionality for your app. Examples of WidgetKit views are WidgetConfiguration (WidgetKit), WidgetView (WidgetKit), Link (WidgetKit), etc.
  1. How do you handle user input in SwiftUI?

In SwiftUI, you can handle user input in a variety of ways, depending on the type of input and the context in which it occurs, and you can also create your own custom views and gestures to suit your specific needs. Here are some common approaches:

  • Use the Button view to create a button that the user can tap to trigger an action. You can attach a closure to the button using the action parameter, which will be executed when the button is tapped.
  • Use the TextField view to create a text input field that the user can type into. You can read the current value of the text field using a @State property, and update it in response to user input using the onChange modifier.
  • Use the Slider view to create a slider that the user can drag to select a value from a range. You can read the current value of the slider using a @State property, and update it in response to user input using the onChange modifier.
  • Use the Toggle view to create a switch that the user can flip to turn a setting on or off. You can read the current value of the toggle using a @State property, and update it in response to user input using the onChange modifier.
  • Use gesture recognizers, such as TapGesture, LongPressGesture, or DragGesture, to detect user input that doesn’t map directly to a specific control, such as taps, swipes, or drags. You can attach a closure to a gesture recognizer using the gesture modifier, which will be executed when the gesture is detected.
  • Use the NavigationLink view to create a link that the user can tap to navigate to a new view. You can attach a destination view to the link using the destination parameter, which will be displayed when the user taps the link.
  1. How do you navigate between views in SwiftUI?

In SwiftUI, you can navigate between views using several different techniques, depending on the specific requirements of your app. Here are some common approaches:

  • Use the NavigationView and NavigationLink views to create a navigation interface for your app. You can wrap your main view in a NavigationView, and then use NavigationLink views to create links to other views in your app. When the user taps a link, the destination view is pushed onto the navigation stack and displayed on the screen.
  • Use the sheet, popover, or fullScreenCover modifier to present a new view as a modal sheet on top of the current view. Modal views are typically used for transient tasks, such as displaying a settings screen or a login form. When the user is done with the modal view, they dismiss it by swiping it down or tapping a button.
  • Use the TabView view to create a tab bar interface for your app. A tab bar interface allows the user to switch between different sections or modes of your app by tapping on tabs at the bottom of the screen.
  • If none of the built-in navigation techniques fit your needs, you can create your own custom navigation solution using gesture recognizers and transitions. For example, you could create a swipe gesture that slides a new view in from the side, or a pinch gesture that zooms in to reveal a detail view.
  1. Difference between ObservableObject and @StateObject?

ObservableObject is a protocol that defines an object that can be observed and updated by SwiftUI, while @StateObject is a property wrapper that provides a way to create and manage an instance of an ObservableObject within a view hierarchy.

You typically use @StateObject when you need to create a new instance of an object that is owned by a specific view hierarchy, and you use ObservableObject when you need to share the state of an object across multiple views.

class User: ObservableObject {
  @Published var name = "John"
}

struct ContentView: View {
  @StateObject var user = User()

  var body: some View {
    VStack {
      Text("Hello, \(user.name)!")
      TextField("Enter your name", text: $user.name)
    }
  }
}
  1. What is the role of modifiers and how do you use them?

In SwiftUI, modifiers are used to modify the appearance or behavior of views. They allow you to customize various aspects of a view, such as its font, color, alignment, layout, and more.

Modifiers are applied to a view using a dot syntax, with the modifier name following the view it’s modifying. They can also be chained together to apply multiple modifications to a view.

Text("Hello, world!")
  .font(.headline)
  .padding()
  1. What is the purpose of a container view and how is it used?

A container view is a type of view that can contain other views, help you create more complex and customizable user interfaces. By nesting container views inside each other, you can create layouts that are flexible and adaptive to different screen sizes and orientations.

Examples of container views in SwiftUI include VStack, HStack, ZStack, List, and Form. They are useful for grouping related views together and controlling their layout and spacing. For example, the VStack container view can be used to stack views vertically, while the HStack container view can be used to stack views horizontally.

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello")
        .font(.largeTitle)
        .foregroundColor(.blue)
      Text("World")
        .font(.title)
        .foregroundColor(.green)
    }
  }
}
  1. How do you use Combine with SwiftUI?

Combine is a framework that provides a declarative and reactive way to handle asynchronous events and data streams. You can use Combine with SwiftUI to create dynamic and responsive user interfaces that react to changes in data and state.

To use Combine with SwiftUI, you can use property wrappers such as @Published, @ObservedObject, and @StateObject to mark your data sources as observable and bind them to your views. You can also use the $ operator to access a publisher for a property and subscribe to its changes using operators such as map, filter, debounce, etc. You can also use the sink or assign methods to handle the values or errors emitted by a publisher.

  1. How do you support multiple screen sizes and orientations?
  • Using flexible layout containers such as VStack, HStack, ZStack, LazyVStack, LazyHStack, etc. that can adapt to different screen widths and heights.
  • Using modifiers such as .frame, .padding, .edgesIgnoringSafeArea, .aspectRatio, etc. to adjust the size and position of your views relative to the screen or their parent views.
  • Using GeometryReader to access the size and coordinate space of the parent view and use it to calculate the size and position of your views dynamically.
  • Using EnvironmentValues such as horizontalSizeClass and verticalSizeClass to get the current size class of the device and use it to apply different modifiers or views based on the size class.
  • Using UIDevice.orientationDidChangeNotification to get the current orientation of the device and use it to apply different modifiers or views based on the orientation.
  • Using @Environment(\.scenePhase) to get the current scene phase of the app and use it to handle changes in orientation or size class when the app moves between foreground and background states.
  1. How do you implement gesture recognition in SwiftUI?

Gesture recognition in SwiftUI can be implemented using the .gesture() modifier, which can be added to any view. There are many types of gestures that can be used, such as TapGesture, DragGesture, LongPressGesture, and RotationGesture.

struct DragGestureView: View {
  @State private var isDragging = false

  var drag: some Gesture {
    DragGesture()
      .onChanged { _ in self.isDragging = true }
      .onEnded { _ in self.isDragging = false }
  }

  var body: some View {
    Circle()
      .fill(self.isDragging ? Color.red : Color.blue)
      .frame(width: 100, height: 100, alignment: .center)
      .gesture(drag)
  }
}

When you add multiple gestures to your app’s view hierarchy, you need to decide how the gestures interact with each other. You use gesture composition to define the order SwiftUI recognizes gestures. There are three gesture composition types: SimultaneousGesture, SequenceGesture, ExclusiveGesture.

  1. What are some best practices for optimizing performance?
  • Minimize the number of view updates by using the Equatable protocol to compare values for equality. This can help prevent unnecessary updates when the data has not actually changed.
  • Use lazy loading and caching to avoid unnecessary computation and data retrieval. For example, you can use the onAppear modifier to load data only when a view is about to appear on the screen.
  • Avoid unnecessary animation and transition effects, as they can slow down the app and consume unnecessary resources.
  • Use lazy stacks and grids when displaying large lists of views. Lazy stacks and grids only create the views that are currently visible on the screen, which can save memory and improve performance.
  • Avoid expensive operations in the body of a view. The body of a view is a pure function that should only create and return a view description. It should not perform any side effects, such as changing state, dispatching work to other threads, or performing network requests.
  • Use modifiers wisely and avoid unnecessary nesting, they can create intermediate views that consume memory and affect performance, prefer using built-in modifiers over custom ones.
  • Finally, use profiling tools such as Instruments to identify performance bottlenecks and optimize your app’s performance.
  1. What is SwiftUI, and how is it different from UIKit?

SwiftUI was introduced by Apple in 2019, and designed to help developers build user interfaces for their apps across all of Apple’s platforms, including iOS, macOS, watchOS, and tvOS. SwiftUI is different from UIKit in several ways:

  • SwiftUI is declarative, which means that developers can describe what they want the user interface to look like, and the framework handles the implementation details. This is different from UIKit, which is imperative, and requires developers to write code that specifies how the user interface should be created.
  • SwiftUI is cross-platform, which means that developers can use the same code to build user interfaces across all of Apple’s platforms. This is different from UIKit, which is specific to iOS.
  • SwiftUI is designed to work well with other frameworks such as Combine, Core Data, and CloudKit, which makes it easier for developers to build complete applications that integrate with other Apple services and technologies.