On this article I’ve gathered my high 10 favourite fashionable UIKit suggestions that I might undoubtedly wish to know earlier than I begin my subsequent challenge.
UIKit
Customized UIColor with darkish mode help
Darkish mode and light-weight mode should not comply with the very same design patterns, generally you need to make use of a border when your app is in gentle mode, however in darkish mode you would possibly wish to conceal the additional line.
One doable answer is to outline a customized UIColor based mostly the given UITraitCollection
. You possibly can verify the userInterfaceStyle
property of a trait to verify for darkish look fashion.
extension UIColor {
static var borderColor: UIColor {
.init { (trait: UITraitCollection) -> UIColor in
if trait.userInterfaceStyle == .darkish {
return UIColor.clear
}
return UIColor.systemGray4
}
}
}
Primarily based on this situation you may simply return completely different colours each for gentle and darkish mode. You possibly can create your individual set of static coloration variables by extending the UIColor object. It is a will need to have little trick in case you are planning to help darkish mode and also you’d wish to create customized colours. 🌈
Observing trait assortment adjustments
This subsequent one can be associated to darkish mode help, generally you’d wish to detect look adjustments of the consumer interface and that is the place the traitCollectionDidChange
perform may be useful. It is obtainable on views, controllers and cells too, so it is fairly an common answer.
class MyCustomView: UIView {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
guard traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) else {
return
}
layer.borderColor = UIColor.borderColor.cgColor
}
}
For instance, inside this perform you may verify if the trait assortment has a special look fashion and you may replace your CoreGraphics layers in line with that. The CoreGraphics framework is a low stage instrument and if you happen to work with layers and colours you need to manually replace them if it involves darkish mode help, however the traitCollectionDidChange
technique might help you a large number. 💡
UIButton with context menus
Creating buttons received loads simpler with iOS 15, however do you know that you may additionally use a button to show a context menu? It’s extremely straightforward to current a UIMenu you simply should set the menu and the showsMenuAsPrimaryAction
property of the button to true.
import UIKit
class TestViewController: UIViewController {
weak var button: UIButton!
override func loadView() {
tremendous.loadView()
let button = UIButton(body: .zero)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
self.button = button
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.leadingAnchor.constraint(equalTo: view.leadingAnchor),
button.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.heightAnchor.constraint(equalToConstant: 44),
])
}
override func viewDidLoad() {
tremendous.viewDidLoad()
button.setTitle("Open menu", for: .regular)
button.setTitleColor(.systemGreen, for: .regular)
button.menu = getContextMenu()
button.showsMenuAsPrimaryAction = true
}
func getContextMenu() -> UIMenu {
.init(title: "Menu",
youngsters: [
UIAction(title: "Edit", image: UIImage(systemName: "square.and.pencil")) { _ in
print("edit button clicked")
},
UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in
print("delete action")
},
])
}
}
This manner the UIButton will act as a menu button, you may assign varied actions to your menu merchandise. I consider this API is particularly useful in some circumstances, these days I want to make use of context menus as an alternative of swipe-to-x-y actions, as a result of it is a bit extra handy for the consumer if we visually present them (normally with 3 dots) that there are extra actions obtainable on a given UI factor. 🧐
Do not be afraid of subclassing views
UIKit is an OOP framework and I extremely advocate to subclass customized views as an alternative of multi-line view configuration code snippets inside your view controller. The earlier code snippet is a superb instance for the alternative, so let’s repair that actual fast.
import UIKit
class MenuButton: UIButton {
@obtainable(*, unavailable)
override init(body: CGRect) {
tremendous.init(body: body)
self.initialize()
}
@obtainable(*, unavailable)
required public init?(coder: NSCoder) {
tremendous.init(coder: coder)
self.initialize()
}
public init() {
tremendous.init(body: .zero)
self.initialize()
}
open func initialize() {
self.translatesAutoresizingMaskIntoConstraints = false
setTitle("Open menu", for: .regular)
setTitleColor(.systemGreen, for: .regular)
menu = getContextMenu()
showsMenuAsPrimaryAction = true
}
func getContextMenu() -> UIMenu {
.init(title: "Menu",
youngsters: [
UIAction(title: "Edit", image: UIImage(systemName: "square.and.pencil")) { _ in
print("edit button clicked")
},
UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in
print("delete action")
},
])
}
func layoutConstraints(in view: UIView) -> [NSLayoutConstraint] {
[
centerYAnchor.constraint(equalTo: view.centerYAnchor),
leadingAnchor.constraint(equalTo: view.leadingAnchor),
trailingAnchor.constraint(equalTo: view.trailingAnchor),
heightAnchor.constraint(equalToConstant: 44),
]
}
}
class TestViewController: ViewController {
weak var button: MenuButton!
override func loadView() {
tremendous.loadView()
let button = MenuButton()
view.addSubview(button)
self.button = button
NSLayoutConstraint.activate(button.layoutConstraints(in: view))
}
override func viewDidLoad() {
tremendous.viewDidLoad()
}
}
As you may see the code contained in the view controller is closely lowered and many of the button configuration associated logic is now encapsulated contained in the MenuButton
subclass. This strategy is nice as a result of you may focus much less on view configuration and extra on your small business logic contained in the view controller. It’s going to additionally enable you to to assume in reusable elements.
One extra be aware right here is that I are inclined to create my interfaces from code that is why I mark the pointless init strategies with the @obtainable(*, unavailable)
flag so different individuals in my workforce cannot name them by accident, however that is only a private choice. 😅
At all times massive navigation title
I do not find out about you, however for me all of the apps have glitches if it involves the big title characteristic within the navigation bar. For private initiatives I’ve received sick and uninterested in this and I merely drive the big title show mode. It is comparatively easy, here is how one can do it.
import UIKit
class TestNavigationController: UINavigationController {
override init(rootViewController: UIViewController) {
tremendous.init(rootViewController: rootViewController)
initialize()
}
@obtainable(*, unavailable)
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
initialize()
}
open func initialize() {
navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .all the time
navigationBar.tintColor = .systemGreen
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.backgroundColor = .systemBackground
navigationBar.standardAppearance = navBarAppearance
navigationBar.scrollEdgeAppearance = navBarAppearance
}
}
class TestViewController: UIViewController {
override func loadView() {
tremendous.loadView()
view.addSubview(UIView(body: .zero))
}
}
let controller = TestNavigationController(rootViewController: TestViewController())
You simply should set two properties (you may subclass UINavigationController
or set these inside your view controller, however I want subclassing) plus you need to add an empty view to your view hierarchy to forestall collapsing in case you are planning to make use of a UIScrollView, UITableView or UICollectionView contained in the view controller.
Since this tip can be based mostly on my private choice, I’ve additionally included a couple of extra customization choices within the snippet. In the event you check out the initialize technique you may see how one can change the tint coloration and the background coloration of the navigation bar. 👍
Customized separators for navigation and tab bars
Since many apps want to have a personalized navigation bar and tab bar look it is fairly a typical apply when you need to additionally add a separator line to differentiate consumer interface components a bit extra. That is how one can remedy it by utilizing a single bar separator class.
import UIKit
class BarSeparator: UIView {
let peak: CGFloat = 0.3
init() {
tremendous.init(body: CGRect(x: 0, y: 0, width: 0, peak: peak))
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .systemGray4
}
@obtainable(*, unavailable)
required init?(coder: NSCoder) {
tremendous.init(coder: coder)
}
func layoutConstraints(for navigationBar: UINavigationBar) -> [NSLayoutConstraint] {
[
widthAnchor.constraint(equalTo: navigationBar.widthAnchor),
heightAnchor.constraint(equalToConstant: CGFloat(height)),
centerXAnchor.constraint(equalTo: navigationBar.centerXAnchor),
topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
]
}
func layoutConstraints(for tabBar: UITabBar) -> [NSLayoutConstraint] {
[
widthAnchor.constraint(equalTo: tabBar.widthAnchor),
heightAnchor.constraint(equalToConstant: CGFloat(height)),
centerXAnchor.constraint(equalTo: tabBar.centerXAnchor),
topAnchor.constraint(equalTo: tabBar.topAnchor),
]
}
}
class MyNavigationController: UINavigationController {
override func viewDidLoad() {
tremendous.viewDidLoad()
let separator = BarSeparator()
navigationBar.addSubview(separator)
NSLayoutConstraint.activate(separator.layoutConstraints(for: navigationBar))
}
}
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
tremendous.viewDidLoad()
let separator = BarSeparator()
tabBar.addSubview(separator)
NSLayoutConstraint.activate(separator.layoutConstraints(for: tabBar))
}
}
This manner you may reuse the BarSeparator
part so as to add a line to the underside of a navigation bar and to the highest of a tab bar. This snippet follows the very same ideas that I confirmed you earlier than, so you need to be acquainted with the subclassing ideas by now. 🤓
Customized tab bar objects
I struggled rather a lot with tab bar merchandise icon alignment, however this the way in which I can simply present / conceal the title and align the icons to the middle of the bar if there aren’t any labels.
import UIKit
class MyTabBarItem: UITabBarItem {
override var title: String? {
get { hideTitle ? nil : tremendous.title }
set { tremendous.title = newValue }
}
non-public var hideTitle: Bool {
true
}
non-public func offset(_ picture: UIImage?) -> UIImage? {
if hideTitle {
return picture?.withBaselineOffset(fromBottom: 12)
}
return picture
}
public comfort init(title: String?, picture: UIImage?, selectedImage: UIImage?) {
self.init()
self.title = title
self.picture = offset(picture)
self.selectedImage = offset(selectedImage)
}
override init() {
tremendous.init()
}
@obtainable(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been applied")
}
}
tabBarItem = MyTabBarItem(title: "House", picture: UIImage(systemName: "home"), selectedImage: nil)
I might additionally like to say that SF Symbols are superb. In case you are not utilizing these type of icons simply but I extremely advocate to have a look. Apple made a very nice job with this assortment, there are such a lot of pretty icons that you should use to visually enrich your app, so do not miss out. 😊
loadView vs viewDidLoad
Lengthy story quick, you must all the time instantiate and place constraints to your views contained in the loadView technique and configure your views contained in the viewDidLoad perform.
I all the time use implicitly unwrapped weak
non-compulsory variables for customized views, for the reason that addSubview perform will create a robust reference to the view when it’s added to the view hierarchy. We do not wish to have retain cycles, proper? That’d be actual unhealthy for our utility. 🙃
import UIKit
class MyCollectionViewController: ViewController {
weak var assortment: UICollectionView!
override func loadView() {
tremendous.loadView()
view.addSubview(UIView(body: .zero))
let assortment = UICollectionView(body: .zero, collectionViewLayout: UICollectionViewFlowLayout())
assortment.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(assortment)
self.assortment = assortment
NSLayoutConstraint.activate([
])
}
override func viewDidLoad() {
tremendous.viewDidLoad()
assortment.backgroundColor = .systemBackground
assortment.alwaysBounceVertical = true
assortment.dragInteractionEnabled = true
assortment.dragDelegate = self
assortment.dropDelegate = self
if let flowLayout = assortment.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.sectionHeadersPinToVisibleBounds = true
}
assortment.register(MyCell.self,
forCellWithReuseIdentifier: MyCell.identifier)
}
Anyway, I might go along with a customized subclass for the gathering view right here as effectively and possibly outline a configure technique then name that one as an alternative of putting the whole lot on to the controller. The choice is all the time up-to-you, I am simply making an attempt to point out you the some doable options. 😉
Stack views & auto-layout anchors
Reap the benefits of stack views and auto structure anchors as a lot as doable. If you will create consumer interfaces programmatically in Swift with the assistance of UIKit, then it’ll be a necessary talent to grasp these strategies in any other case you are going to battle loads.
I have already got a tutorial about utilizing auto structure programmatically and one other one about mastering auto-layout anchors, they have been revealed a couple of years in the past, however the ideas are nonetheless legitimate and the code nonetheless works. I even have yet one more article that you must learn if you wish to study about constructing types utilizing stack views. Studying these type of issues helped me loads to create complicated screens hassle-free. I am additionally utilizing yet one more “finest apply” to create assortment views.
When SwiftUI got here out I had the sensation that ultimately I might do the identical with UIKit, however after all Apple had the required tooling to help the framework with view builders and property wrappers. Now that we have now SwiftUI I am nonetheless not utilizing it as a result of I really feel prefer it lacks various options even in 2022. I do know it is nice and I’ve created a number of prototypes for screens utilizing it, but when it involves a posh utility my intestine tells me that I ought to nonetheless go along with UIKit. 🤐
Create a reusable elements library
My remaining recommendation on this tutorial is that you must construct a customized Swift bundle and transfer all of your elements there. Perhaps for the primary time it’ll eat various time however in case you are engaged on a number of initiatives it’ll velocity up improvement course of to your second, third, and so forth. app.
You possibly can transfer all of your customized base lessons right into a separate library and create particular ones to your utility. You simply should mark them open, you should use the supply API to handle what can be utilized and what ought to be marked as unavailable.
I’ve various tutorials concerning the Swift Package deal Supervisor on my weblog, this can be a nice approach to get acquainted with it and you can begin constructing your individual library step-by-step. 😊