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.
.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.
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.
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.
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.
@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.
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.
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
}
}
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.
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.
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:
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.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.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.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.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.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.In SwiftUI, you can navigate between views using several different techniques, depending on the specific requirements of your app. Here are some common approaches:
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.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.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.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)
}
}
}
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()
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)
}
}
}
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.
VStack
, HStack
, ZStack
, LazyVStack
, LazyHStack
, etc. that can adapt to different screen widths and heights..frame
, .padding
, .edgesIgnoringSafeArea
, .aspectRatio
, etc. to adjust the size and position of your views relative to the screen or their parent views.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.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.UIDevice.orientationDidChangeNotification
to get the current orientation of the device and use it to apply different modifiers or views based on the orientation.@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.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
.
onAppear
modifier to load data only when a view is about to appear on the screen.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: