Abstract

In the last month of 2021, the project team received a demand for an ipad-APP demo. Since large-scale toC launch was not involved, SwiftUI was used to practice. What could be the happiest way to work than using the latest tools to solve problems?

It took two weeks to realize functions while reading documents. Without any UI involvement, SwiftUI was used to realize a demo-App that satisfied the demander very much. The author thinks that he has some aesthetic problems. It is impossible for me to use Swift to build an app without UI. But using SwiftUI, it can also have a more appropriate visual effect without UI (full of apple style).

Roughly yy the body sense of SwiftUI

  • You can finally get started without a UI.
  • The CANVAS for real-time UI display is not as smart as the H5 or Flutter, but it is much better than the one that requires a rebuild, although sometimes it is not as real-time
  • The layers become a lot more. When I clicked on the layer at run time, I could see that the View layer became very complicated. It can be seen that SwiftUI is wrapped on TOP of UIKit.
  • The state monitoring mechanism of View needs to be paid attention to. Random monitoring can cause performance problems. For example, a page listens to many state objects in a unified way, and the change of each object will cause the page to refresh. Consider splitting the page into sub-pages to listen for the corresponding objects.

Follow-up is notes, less content, follow-up encountered and then add.

State, StateObject, ObservedObject

State State management based on value types and is only owned on the current page

StateObject is similar to ObservedObject in that it is object-based State management, except that State is a strong reference to an object and Observerd is a weak reference. If the listening object is strongly referenced by another object or is a singleton, use ObservedObject; otherwise, use StateObject.

Remember: Using ObservedObject where StateObject should be used will raise a ghost event.

AppStorage SwiftUI UserDefaults

It is simple and rough to use

@appStorage ("hehe_key") var hehe = ""Copy the code

The Realm of the DB

Realm works perfectly with swiftUI. Check out our website for details

import SwiftUI
import RealmSwift

class RealmSampleSetting: Object, ObjectKeyIdentifiable {
    @Persisted var sampleRate = "1k"
    @Persisted var HPFEnable = false
    @Persisted var hpfValue = 30
    
    static func get() -> RealmSampleSetting {
        let realm = try! Realm()
        if let item = realm.objects(RealmSampleSetting.self).first {
            return item
        }
        let setting = RealmSampleSetting()
        try? realm.write {
            realm.add(setting)
        }
        return setting
    }
}

Copy the code

This configuration will have only one record in the table

The following are examples of modifications

struct SampleRateView: View { @ObservedRealmObject var ds: RealmSampleSetting = RealmSampleSetting.get() private var realm = try! Realm() var body: Some View {VStack {Text(" sample parameter Settings ").bold().padding(20) List {HStack {Text(" sample rate ") Spacer() Picker("", selection: Binding.init(get: { return RatingSpeed(rawValue: ds.sampleRate) ?? .one }, set: { v in let tmp = ds.thaw() try? realm.write { tmp? .sampleRate = v.rawValue } })) { ForEach(RatingSpeed.allCases, id: \.rawValue) { speed in Text(speed.rawValue).tag(speed) } } .pickerStyle(.segmented) .frame(width: 300) } HStack { Toggle("", isOn: $ds.HPFEnable).labelsHidden() Text("HPF(HZ)") Spacer() TextField.init("", value: $ds.hpfValue, format:.number.precision(.integerLength(2... 3))) .keyboardType(.numberPad) .multilineTextAlignment(.trailing) .frame(width: 200) .disabled(! ds.HPFEnable) } } } } }Copy the code

For simple modifications, you can use the corresponding attribute directly. For complex Binding, you need to call save

Control article

Segment

enum RatingSpeed: String, CaseIterable {case one = "1k" case three = "3.9k" case five = "5k"} @state private var speedPicker = ratingspee.oneCopy the code

The body of the UI

Picker(" speed selection ", Selection: $speedPicker) {ForEach(ratingspee.allcases, id: \.rawValue) { speed in Text(speed.rawValue).tag(speed) } }.pickerStyle(.segmented) .onChange(of: speedPicker) { picker in BluetoothCommand.speed(picker) }Copy the code

Better – Without defining speedPicker variables, naming is always one of the most time-consuming tasks in programming

Picker("", selection: Binding(get: { return RatingSpeed(rawValue: ds.sampleRate) ?? .one }, set: { v in let tmp = ds.thaw() try? realm.write { tmp? .sampleRate = v.rawValue } })) { ForEach(RatingSpeed.allCases, id: \.rawValue) { speed in Text(speed.rawValue).tag(speed) } } .pickerStyle(.segmented) .frame(width: 300)Copy the code

Binding get set. get gets the initial value. Set is the modified processing

Performance Optimization

SwiftUI in Tools and Instruments

  1. The number of page refreshes in the View Body tells you which pages are being refreshed.

Optimization scheme

  1. Determine if the page should be refreshed. Generally, data listening is used.
  2. Encapsulate the data listening control as a separate View to listen for data. This will not affect the data refresh of other data sources.
struct MainControlView: View { @ObservedObject var blueCentral = BluetoothCentral.shared var body: some View { VStack(alignment: .center, spacing: 32) { TimelineView(.periodic(from: Date(), by: 1)) {context in TimeShow(date: context.date, durationTime: $durationTime, timeRefresh: $timeRefresh)} Text(" times per second: \ (String (format: "% 1 f", blueCentral. PointsPerSecond)) ") the Text (packet loss rate: \ "(String (format: "%.2f", bluecentral.droprate *100))%") Text(" Sampling speed: \(String(format: "%.2f", bluecentral.rattingSpeed))")}}}Copy the code

Before optimization, every time blueCentral data changes, the time display in TimelineView will be affected. However, the time display has nothing to do with blueCentral. In this case, the blueCentral related controls can be encapsulated as separate controls

Struct DataShowView: View {@observedobject var blueCentral = bluetoothcentral.shared var body: some View {Text(" \ (String (format: "% 1 f", blueCentral. PointsPerSecond)) ") the Text (packet loss rate: \ "(String (format: "%.2f", bluecentral.droprate *100))%") Text(" Sample speed: \(String(format: "%.2f", blueCentral.rattingSpeed))") } } struct MainControlView: View { var body: some View { VStack(alignment: .center, spacing: 32) { TimelineView(.periodic(from: Date(), by: 1)) { context in TimeShow(date: context.date, durationTime: $durationTime, timeRefresh: $timeRefresh) } DataShowView() } } }Copy the code

This way, bluecentral’s data refresh does not affect TimelineView

New insights will continue to be updated