Customized UIColor with darkish mode assist
Darkish mode and lightweight mode should not observe the very same design patterns, typically you want to make use of a border when your app is in mild mode, however in darkish mode you would possibly need to disguise the additional line.
One attainable answer is to outline a customized UIColor primarily based the given UITraitCollection. You possibly can examine the userInterfaceStyle property of a trait to examine for darkish look type.
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 totally different colours each for mild and darkish mode. You possibly can create your personal set of static shade variables by extending the UIColor object. It is a will need to have little trick if you’re planning to assist darkish mode and also you’d prefer to create customized colours. 🌈
Observing trait assortment adjustments
This subsequent one can also be associated to darkish mode assist, typically you’d prefer to detect look adjustments of the consumer interface and that is the place the traitCollectionDidChange operate might 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 operate you may examine if the trait assortment has a special look type and you’ll replace your CoreGraphics layers in response to that. The CoreGraphics framework is a low stage software and if you happen to work with layers and colours you must manually replace them if it involves darkish mode assist, however the traitCollectionDidChange methodology will help you a large number. 💡
UIButton with context menus
Creating buttons acquired loads simpler with iOS 15, however do you know you could additionally use a button to show a context menu? It’s totally straightforward to current a UIMenu you simply need to 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",
kids: [
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 numerous actions to your menu merchandise. I consider this API is very helpful in some circumstances, these days I desire to make use of context menus as a substitute of swipe-to-x-y actions, as a result of it’s kind of extra handy for the consumer if we visually present them (often with 3 dots) that there are further actions obtainable on a given UI factor. 🧐
Do not be afraid of subclassing views
UIKit is an OOP framework and I extremely suggest to subclass customized views as a substitute of multi-line view configuration code snippets inside your view controller. The earlier code snippet is a good 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",
kids: [
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 diminished and a lot 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 enterprise logic contained in the view controller. It’s going to additionally show you how to to assume in reusable elements.
One further observe right here is that I are likely to create my interfaces from code that is why I mark the pointless init strategies with the @obtainable(*, unavailable) flag so different folks in my group cannot name them by chance, however that is only a private desire. 😅
All the time massive navigation title
I do not learn about you, however for me all of the apps have glitches if it involves the massive title function within the navigation bar. For private initiatives I’ve acquired sick and uninterested in this and I merely pressure the massive title show mode. It is comparatively easy, here is learn how to 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 need to set two properties (you may subclass UINavigationController or set these inside your view controller, however I desire subclassing) plus you must add an empty view to your view hierarchy to stop collapsing if you’re planning to make use of a UIScrollView, UITableView or UICollectionView contained in the view controller.
Since this tip can also be primarily based on my private desire, I’ve additionally included a couple of extra customization choices within the snippet. If you happen to check out the initialize methodology you may see learn how to change the tint shade and the background shade of the navigation bar. 👍
Customized separators for navigation and tab bars
Since many apps desire to have a personalized navigation bar and tab bar look it is fairly a typical observe when you must additionally add a separator line to differentiate consumer interface components a bit extra. That is how one can clear up 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 element 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 quite a bit with tab bar merchandise icon alignment, however this the way in which I can simply present / disguise the title and align the icons to the middle of the bar if there are not 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. If you’re not utilizing these form of icons simply but I extremely suggest to have a look. Apple made a very nice job with this assortment, there are such a lot of pretty icons that you should utilize to visually enrich your app, so do not miss out. 😊
loadView vs viewDidLoad
Lengthy story brief, you must all the time instantiate and place constraints to your views contained in the loadView methodology and configure your views contained in the viewDidLoad operate.
I all the time use implicitly unwrapped weak non-obligatory variables for customized views, because the addSubview operate will create a robust reference to the view when it’s added to the view hierarchy. We do not need to have retain cycles, proper? That’d be actual dangerous 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 together with a customized subclass for the gathering view right here as nicely and possibly outline a configure methodology then name that one as a substitute of putting every little thing 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 attainable options. 😉
Stack views & auto-layout anchors
Benefit from stack views and auto structure anchors as a lot as attainable. If you will create consumer interfaces programmatically in Swift with the assistance of UIKit, then it is going to be a vital talent to grasp these methods in any other case you are going to wrestle loads.
I have already got a tutorial about utilizing auto structure programmatically and one other one about mastering auto-layout anchors, they had 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 be taught about constructing kinds utilizing stack views. Studying these form of issues helped me loads to create advanced screens hassle-free. I am additionally utilizing yet one more “finest observe” to create assortment views.
When SwiftUI got here out I had the sensation that finally I might do the identical with UIKit, however after all Apple had the required tooling to assist the framework with view builders and property wrappers. Now that we’ve got SwiftUI I am nonetheless not utilizing it as a result of I really feel prefer it lacks numerous 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 together with UIKit. 🤐
Create a reusable elements library
My closing recommendation on this tutorial is that you must construct a customized Swift bundle and transfer all of your elements there. Possibly for the primary time it is going to eat numerous time however if you’re engaged on a number of initiatives it can pace up improvement course of on your second, third, and many others. app.
You possibly can transfer all of your customized base lessons right into a separate library and create particular ones on your utility. You simply need to mark them open, you should utilize the provision API to handle what can be utilized and what ought to be marked as unavailable.
I’ve numerous tutorials in regards to the Swift Bundle Supervisor on my weblog, it is a nice solution to get acquainted with it and you can begin constructing your personal library step-by-step. 😊