Tuesday, June 25, 2024
HomeIOS Development10 little UIKit suggestions it is best to know

10 little UIKit suggestions it is best to know


Customized UIColor with darkish mode help

Darkish mode and lightweight mode should not observe the very same design patterns, generally you want to make use of a border when your app is in mild mode, however in darkish mode you would possibly wish to disguise the additional line.

One doable resolution is to outline a customized UIColor based mostly the given UITraitCollection. You’ll be able to examine the userInterfaceStyle property of a trait to examine for darkish look model.

extension UIColor {
    static var borderColor: UIColor {
        .init { (trait: UITraitCollection) -> UIColor in
            if trait.userInterfaceStyle == .darkish {
                return UIColor.clear
            }
            return UIColor.systemGray4
        }
    }
}

Based mostly on this situation you’ll be able to simply return completely different colours each for mild and darkish mode. You’ll be able to create your individual set of static colour variables by extending the UIColor object. It is a will need to have little trick if you’re planning to help darkish mode and also you’d prefer to create customized colours. 🌈

Observing trait assortment adjustments

This subsequent one can be associated to darkish mode help, generally you’d prefer to detect look adjustments of the consumer interface and that is the place the traitCollectionDidChange perform might be useful. It is out there on views, controllers and cells too, so it is fairly an common resolution.

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’ll be able to examine if the trait assortment has a unique look model and you may replace your CoreGraphics layers in response to that. The CoreGraphics framework is a low degree instrument and when you work with layers and colours you need to manually replace them if it involves darkish mode help, however the traitCollectionDidChange methodology may also help you numerous. πŸ’‘

UIButton with context menus

Creating buttons acquired quite a bit simpler with iOS 15, however do you know which you can 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 fashion the UIButton will act as a menu button, you’ll be able to assign numerous actions to your menu merchandise. I imagine this API is very useful in some instances, these days I favor 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 (normally with 3 dots) that there are further actions out there on a given UI aspect. 🧐

Do not be afraid of subclassing views

UIKit is an OOP framework and I extremely advocate 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 {

    @out there(*, unavailable)
    override init(body: CGRect) {
        tremendous.init(body: body)
        
        self.initialize()
    }

    @out there(*, 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’ll be able to see the code contained in the view controller is closely lowered 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’ll be able to focus much less on view configuration and extra on what you are promoting logic contained in the view controller. It’s going to additionally provide help to to assume in reusable parts.

One further be aware right here is that I are likely to create my interfaces from code that is why I mark the pointless init strategies with the @out there(*, unavailable) flag so different individuals in my workforce cannot name them by accident, however that is only a private desire. πŸ˜…

At all times massive navigation title

I do not learn 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 acquired sick and bored with this and I merely drive the big title show mode. It is comparatively easy, this is tips on how to do it.

import UIKit

class TestNavigationController: UINavigationController {

    override init(rootViewController: UIViewController) {
        tremendous.init(rootViewController: rootViewController)
        
        initialize()
    }

    @out there(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        tremendous.init(coder: aDecoder)

        initialize()
    }
    
    open func initialize() {
        navigationBar.prefersLargeTitles = true
        navigationItem.largeTitleDisplayMode = .at all times
        
        
        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’ll be able to subclass UINavigationController or set these inside your view controller, however I favor subclassing) plus you need to 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 be based mostly on my private desire, I’ve additionally included a couple of extra customization choices within the snippet. Should you check out the initialize methodology you’ll be able to see tips on how to change the tint colour and the background colour of the navigation bar. πŸ‘

Customized separators for navigation and tab bars

Since many apps favor to have a personalized navigation bar and tab bar look it is fairly a typical follow when you need to additionally add a separator line to tell apart consumer interface parts a bit extra. That is how one can remedy it through the use of 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
    }
    
    @out there(*, 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 fashion you’ll be able to 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 try to be aware of the subclassing ideas by now. πŸ€“

Customized tab bar gadgets

I struggled rather a lot 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 aren’t any labels.

import UIKit

class MyTabBarItem: UITabBarItem {
    
    override var title: String? {
        get { hideTitle ? nil : tremendous.title }
        set { tremendous.title = newValue }
    }
        
    personal var hideTitle: Bool {
        true
    }

    personal 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()
    }

    @out there(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been carried out")
    }
}


tabBarItem = MyTabBarItem(title: "Dwelling", picture: UIImage(systemName: "home"), selectedImage: nil)

I might additionally like to say that SF Symbols are wonderful. In case you are not utilizing these form 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 utilize to visually enrich your app, so do not miss out. 😊

loadView vs viewDidLoad

Lengthy story brief, it is best to at all times instantiate and place constraints to your views contained in the loadView methodology and configure your views contained in the viewDidLoad perform.

I at all times use implicitly unwrapped weak elective variables for customized views, because the addSubview perform will create a powerful 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 software. πŸ™ƒ

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 properly 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 at all times up-to-you, I am simply making an attempt to point out you the some doable options. πŸ˜‰

Stack views & auto-layout anchors

Benefit from stack views and auto format anchors as a lot as doable. If you’re going to create consumer interfaces programmatically in Swift with the assistance of UIKit, then it’ll be a vital talent to grasp these strategies in any other case you are going to battle quite a bit.

I have already got a tutorial about utilizing auto format programmatically and one other one about mastering auto-layout anchors, they had been printed 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 it is best to learn if you wish to study about constructing types utilizing stack views. Studying these form of issues helped me quite a bit to create advanced screens hassle-free. I am additionally utilizing yet one more “finest follow” to create assortment views.

When SwiftUI got here out I had the sensation that finally I might do the identical with UIKit, however in fact Apple had the required tooling to help the framework with view builders and property wrappers. Now that we’ve 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 software my intestine tells me that I ought to nonetheless go along with UIKit. 🀐

Create a reusable parts library

My last recommendation on this tutorial is that it is best to construct a customized Swift bundle and transfer all of your parts there. Possibly for the primary time it’ll eat various time however if you’re engaged on a number of initiatives it would velocity up improvement course of in your second, third, and so forth. app.

You’ll be able to transfer all of your customized base courses right into a separate library and create particular ones in your software. You simply should mark them open, you should utilize the supply API to handle what can be utilized and what must be marked as unavailable.

I’ve various tutorials concerning the Swift Package deal Supervisor on my weblog, this can be a nice strategy to get aware of it and you can begin constructing your individual library step-by-step. 😊

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments