Hello. I'm Sugawa, who is involved in the development of the job change navigation iOS app at Livesense.
This time, I will introduce how to incorporate a View created with SwiftUI into a view controller, using the job change navigation app as an example.
Also, I would like to use SwiftUI's ** PropertyWrappers
** to realize a method to update the screen at no implementation cost when the displayed data changes.
The job change navigation app has a place to display the search conditions and the number of search results when searching for a job, but the existing implementation is created with a .xib
file.
This time, I would like to introduce SwiftUI by replacing View created with xib with SwiftUI.
(The red frame part of the image is the target View)
The requirements are as follows.
--Tap the ** blue icon ** on the far right to open the search condition setting screen. --When you set the search conditions and close the setting screen, the new search conditions will be reflected. --Search condition setting items -** Job type ** (Multiple selections possible)
First, create a ViewModel that meets the View requirements.
The ViewModel conforms to the ObservableObject
protocol so that when a value is updated, the new value is automatically reflected in the View.
By declaring the property with @Published
, the viewing View will be notified of the change when a value update occurs.
python
class SearchConditionHeaderModel: ObservableObject {
@Published private(set) var workCount = "0"
@Published private(set) var occupation = "Desired occupation"
@Published private(set) var salary = "desired salary"
@Published private(set) var location = "preferred workplace"
var buttonAction: (() -> Void)?
func update(workCount: String, occupation: String, salary: String, location: String) {
self.workCount = workCount
self.occupation = occupation
self.salary = salary
self.location = location
}
}
Next, create the following screen with Swift UI.
Declare your ViewModel with @ ObservedObject
so that you are notified of updates to the ViewModel's properties.
python
@ObservedObject var viewModel: SearchConditionHeaderModel
By default, the preview on the right side of the screen is displayed in the screen size of the iPhone, but if you add a modifier like ↓, it will be displayed in the size of the content and you can also add a margin.
python
struct SearchConditionHeaderView_Previews: PreviewProvider {
static var previews: some View {
SearchConditionHeaderView(viewModel: SearchConditionHeaderModel())
//View preview by content size
.previewLayout(PreviewLayout.sizeThatFits)
//margin
.padding()
}
}
The layout of the calling view controller is created with Storyboard
.
I want to display the View created with SwiftUI in the red frame part of the image, so delete the existing View created with xib
and add ContainerView
to this part.
The added ContainerView
is linked to the view controller with IBOutlet
.
Next, keep the ViewModel created first in the view controller.
python
private var searchConditionModel = SearchConditionHeaderModel()
Call the following function with viewDidLoad ()
of ViewController.
python
private func setupConditionHeaderView() {
// 1.View of SwiftUI wraps in a class that inherits UIHostingController
//Pass ↑ ViewModel to SwiftUI
let hostingVC = SearchConditionHeaderHostingViewController(rootView: SearchConditionHeaderView(viewModel: searchConditionModel))
hostingVC.view.backgroundColor = UIColor(red: 240 / 255, green: 238 / 255, blue: 235 / 255, alpha: 1)
// 2.Set hostingVC as child ViewController
addChild(hostingVC)
// 3.Added hostingVC view to ContainerView subView
conditionHeaderView.addSubview(hostingVC.view)
// 4.Call that a child view controller has been added to the parent view controller
hostingVC.didMove(toParent: self)
// 5.Fix view with constraint
hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
let bindings = ["view": hostingVC.view]
conditionHeaderView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0),
metrics: nil,
views: bindings as [String: Any]))
conditionHeaderView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0),
metrics: nil,
views: bindings as [String: Any]))
// 6.Action when the View button in Swift UI is tapped
searchConditionModel.buttonAction = { [weak self] in
self?.showMatchingCondition()
}
}
}
I will explain the code.
viewWillAppear ()
of UIHostingController, but if there is no part I want to override, it is OK to wrap SwiftUI with UIHostingController. Also, since the viewModel is required to initialize the SwiftUI, we are passing the viewModel called by the view controller..didMove (toParent :)
method. There was a detailed explanation in here.When you start the app, the View implemented in Swift UI will be displayed.
After that, you can bind the search condition to SwiftUI by executing the update ()
method defined in ViewModel when the search condition is updated.
SearchConditionHeaderModel.swift
func update(workCount: String, occupation: String, salary: String, location: String) {
self.workCount = workCount
self.occupation = occupation
self.salary = salary
self.location = location
}
Before After I changed the font and text color slightly, but I was able to replace the equivalent of View made with xib with SwiftUI.
Before | After |
---|---|
xib | SwiftUI |
This time I tried adding SwiftUI experimentally, but when creating a View with SwiftUI, it took time to display the Preview, and it was difficult to check the layout. Every time I update the Preview, a differential build runs, and the update is completed several tens of seconds after the build is completed, so if you want to introduce Swift UI in earnest, you need to seek to improve the Preview speed It seems likely. Also, I was wondering which Property Wrappers to use depending on the situation, so I felt that I needed to understand Property Wrappers a little more. It was easier than I expected, so I'd like to use SwiftUI when adding new screens in the future!
Recommended Posts