As software engineers of BetterMe, we always try to adopt the latest technologies and tools, so we decided to try SwiftUI in our new project. Then, a few weeks later, we refused this idea for a lot of reasons (lack of documentation, development risks, estimates, strange bugs) and moved back to UIKit.
Although we use UIKit again, we saved some of SwiftUI’s features and advantages:
- Preview for your views. It allows you to see your view in different states, locales, screen sizes, dark mode, etc. Because our apps have a lot of locales and different states of UI, it takes a lot of time to check all possible states manually. When you use data-driven architecture it’s very easy to describe all these states for Xcode Preview.
- Debugging, building and displaying UI at the same tab in realtime. It takes a lot of time to run a simulator to check that you have fixed the UI bug or that it’s correct when you’re building a new one. Moreover, you can build UI from the down level to the top now, because you see the appearance of your views immediately.
This article is about what we have come to.
- Xcode 11+ (the newer the better, latest versions have more stable Xcode Previews)
- A project with a target for iOS 13+. If your project should support lower versions — create another scheme that supports iOS 13.0+ or change deployment target to iOS 13.0 for Debug configuration.
Xcode Preview is a great tool to see your UI without the need to run the app on a real device or simulator. Although it works with SwiftUI, it allows us to inject and display UIKit views. This possibility is provided by protocols —
UIViewController. There are a lot of articles about the integration of Xcode Previews for UIKit. Some of them provide generic solutions, others provide non-generic. I will briefly show you our one.
UIView in Xcode Preview all you need is a container which implements two required methods of
To use Xcode Preview with
UIViewController you should implement a container for another protocol —
UIViewControllerRepresentable. It also requires two methods.
Create a sample view
Let’s create some
UIView to play with Xcode Preview.
Create and add new
UIView with Xib to your project, let's name it
UILabel and a few constraints to set the position of the label.
UILabel to IBOutlet property in the class, call it titleLabel. Now let’s add a possibility to set the text of the label from outside of the view. We use so-called Props object to update the state of views and view controllers via the single render method.
To see our
TestViewin Xcode Preview just add the following lines at the end of the file.
Now press ⌘+⌥+ Return to open Xcode Preview or open Editor Options at the top right corner of File Inspector and select Canvas.
Then press ⌘+⌥+P or Resume on Canvas. You should see something like this on Xcode Preview window.
Note: If you experience any problem with Xcode Preview — go to the Troubleshooting chapter.
Note: This works the same way for
UIViewController, so I won't describe the process, you just should use
DebugPreviewViewand instantiate the view controller by loading from Storyboard or via init if you don't have one.
What is handy is that it will do hot reload for preview every time you change some properties or constraints. In case it doesn’t, run Resume preview again by pressing ⌘+⌥+P.
Go away from Xib and Storyboard
Soon we realized that moving between Interface Builder and File Inspector (to take a look at the Preview) takes too much time and it’s not so handy.
Solution: grab all our xibs and storyboards, move them to the bin and start building interface by code only. To simplify the layout building and updating process we use https://github.com/SnapKit/SnapKit but you may want to directly use
NSLayoutConstraint to build your own interface.
SnapKit and update our code:
TestView.xibbecause you don't need it anymore.
import SnapKitstatement at the top of
4. Rewrite Preview block to init
TestView without nib:
We should get the same result in the preview as earlier but now we can build all our interface by code. The advantages of this solution are clear — you immediately see what are you doing, it’s easier to change and it saves you a lot of time.
Preview container with a specific/automatic size
Solution #1. Specify the size of the container.
Add the modifier to the container to specify the size:
}.previewLayout(.fixed(width: 375, height: 100))
Well, now it’s exactly the size you specified but sometimes it isn’t what you want.
Solution #2. Implement a UIKit container to calculate the intrinsic content size of the child view
There is a preview modifier that should shrink the view to the smallest possible size to fit all the content.
It works well for SwiftUI views but it has no effect when working with
UIView. I've spent some time and figured out that it relies on view's
We should create another container to automatically calculate the intrinsic content size of the child view using AutoLayout and return this value as it’s own intrinsic content size.
Let’s define an enumeration to describe the layout we want to reach:
Then we should create a
UIView container that will calculate the size of the child view differently based on
Also, we need to update our
DebugPreviewView to wrap all the views into
Let’s go back to
TestView and apply new parameters to preview. Now we pass the previewLayout parameter and tell the preview to fit the smallest size possible by using .sizeThatFits modifier.
To see the changes let’s resume the Xcode Preview. You should get something like that.
Note: This solution requires advanced knowledge of Auto Layout. It may not render the correct size of your view because it’s not too explicit which size your view should be. In that case, try to play with constraints and content hugging and/or compression resistance to describe your intents clearly.
Make previews of different states
One of the main advantages of Xcode Preview is that you could describe different states of your view and preview all of them at once.
Add somewhere this line somewhere in init to add support of the dark mode for our TestView:
backgroundColor = UIColor.systemBackground
Let’s add another preview to see how it looks in dark mode. Add this at the end of the
Be free to play with different locales, device types, sizes, etc. It’s very handy and could save you a lot of time. Visit Apple documentation to inspect other preview modifiers of SwiftUI’s View.
Handy setup function to use with lazy initializers
This function is easy to use with any
It saves a lot of lines in the long distance.
In some cases, you may need a runtime check of your view’s behavior. It doesn’t work well if you need some of the environment outside of your view but enough to debug custom controls, animations, scroll views and etc.
To start the Live Preview — tap the play button near the preview:
Xcode Preview crashes
From time to time you see that annoying red icon on Xcode Preview telling us that there is something wrong. Top reasons for these crashes:
- Runtime crash. If your code crashes or may crash in real runtime — it also can crash in Xcode Preview. So be careful and check constraints, compiler errors, etc. Also, it’s a good way to check that your constraints are valid at runtime.
- The old version of Xcode. Apple improves Xcode Preview on each update, be free to update Xcode.
- Xcode Preview doesn’t work with Firebase installed by CocoaPods at the moment of writing. You should add Firebase dependency via frameworks import https://firebase.google.com/docs/ios/setup#frameworks
Anyway, Xcode Preview provides you the reason for the crash. To see what lead to a crash — tap the button (i) and Show Crash Logs. It will open the folder where you can see the latest crash logs.
Invalid layout in Xcode Preview
It happens if UIKit doesn’t have enough information on how to layout your view properly. Try different and more explicit constraints. Try to change content hugging and compression resistance of some view to reach what you need.
Benefits from our solution
By making UI by code in one file with the preview you automatically gain the next advantages:
- You see the result of your coding in the same window and almost immediately compared to running on the device or simulator.
- You don’t need to run your project so frequently.
- You almost never need to run your project when you are building UI.
- Easier to change UI and debug different states of your views.
- Easier to handle different devices/locales/color schemes and etc. It’s like your own storybook in each
- Easier to animate layout because it becomes simpler to store constraints in properties. Also,
SnapKithas awesome functions to remake or update constraints.
- Decomposition is easier too. How many times you have avoided decomposition because there are too many routines to create a new view and check that it works correctly?
Xcode Previews provide a great way to preview your views. In almost all cases it displays the same picture that you will see on the simulator/device runtime so you don’t even need to run it on the device to debug presentation of your views and controllers.
Save your time, improve your tools. Our time is all we got.
Thank you for long reading, be free to ask any question :)