Files
virtual-controller/remote-client-ios/remote-client-ios/Core/ObjectsViewController.swift
T
Paul Lipscomb 7a74cfa75e iOS remote client: native UIKit surface, WS logging, client-side layout persistence
- Add remote-client-ios: full native UIKit app with Objects/View/UILayout/Functions pattern
- Widget hierarchy: BaseWidget → Fader, Toggle, Transport, Title, Feedback, LogWidget
- LoggingVC: child VC embedded in LogWidget for live WS message inspection
- Codable model: ModelPresetLoadToRemoteDevice with snake_case decoder strategy
- Layout persistence: UserDefaults keyed by preset_uuid, fully client-side
- WS logging: dedicated WS In/Out floating panels on desktop app
- Remove HTTP server and JS client (replaced by native iOS app)
- Add Xcode noise to .gitignore (xcuserstate, xcuserdata)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:02:10 -04:00

158 lines
5.6 KiB
Swift

import UIKit
class ObjectsViewController: UIViewController {
// MARK: - WebSocket
var wsTask: URLSessionWebSocketTask?
var urlSession: URLSession?
// MARK: - Connection State
enum ConnectionState {
case notStarted, connecting, connected, disconnected
}
var connectionState: ConnectionState = .notStarted
// MARK: - State
var presetName: String = ""
var presetUuid: String = ""
var isLocked: Bool = true
// MARK: - Widgets
var widgets: [String: BaseWidget] = [:]
var feedbackWidgets: [FeedbackWidget] = []
// MARK: - Layout
var layout: [String: CGRect] = [:]
let containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor(white: 0.1, alpha: 1)
return view
}()
// MARK: - Toolbar
lazy var connectButton: UIButton = {
let btn = UIButton(type: .system)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Connect", for: .normal)
btn.setTitleColor(.black, for: .normal)
btn.backgroundColor = UIColor(red: 0, green: 0.9, blue: 0.46, alpha: 1)
btn.layer.cornerRadius = 4
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
btn.addTarget(self, action: #selector(self.connectTapped), for: .touchUpInside)
return btn
}()
lazy var arrangeButton: UIButton = {
let btn = UIButton(type: .system)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Arrange", for: .normal)
btn.setTitleColor(.lightGray, for: .normal)
btn.backgroundColor = UIColor(white: 0.15, alpha: 1)
btn.layer.borderWidth = 1
btn.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
btn.layer.cornerRadius = 4
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
btn.addTarget(self, action: #selector(self.arrangeTapped), for: .touchUpInside)
return btn
}()
lazy var lockButton: UIButton = {
let btn = UIButton(type: .system)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Lock / Save", for: .normal)
btn.setTitleColor(.black, for: .normal)
btn.backgroundColor = UIColor(red: 0, green: 0.9, blue: 0.46, alpha: 1)
btn.layer.cornerRadius = 4
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
btn.addTarget(self, action: #selector(self.lockTapped), for: .touchUpInside)
return btn
}()
// MARK: - Connection Fields
let ipField: UITextField = {
let tf = UITextField()
tf.translatesAutoresizingMaskIntoConstraints = false
tf.text = "127.0.0.1"
tf.textColor = .lightGray
tf.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
tf.backgroundColor = UIColor(white: 0.12, alpha: 1)
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
tf.layer.cornerRadius = 4
tf.keyboardType = .decimalPad
tf.textAlignment = .center
tf.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
tf.leftViewMode = .always
tf.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
tf.rightViewMode = .always
return tf
}()
let portField: UITextField = {
let tf = UITextField()
tf.translatesAutoresizingMaskIntoConstraints = false
tf.text = "8765"
tf.textColor = .lightGray
tf.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
tf.backgroundColor = UIColor(white: 0.12, alpha: 1)
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
tf.layer.cornerRadius = 4
tf.keyboardType = .numberPad
tf.textAlignment = .center
tf.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
tf.leftViewMode = .always
tf.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
tf.rightViewMode = .always
return tf
}()
// MARK: - Logging
lazy var loggingView: LoggingView = LoggingView()
lazy var logWidget: LogWidget = {
let w = LogWidget(uid: "log-widget", frame: LogWidget.defaultFrame())
return w
}()
lazy var showLoggingButton: UIButton = {
let btn = UIButton(type: .system)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Show Logging", for: .normal)
btn.setTitleColor(.lightGray, for: .normal)
btn.backgroundColor = UIColor(white: 0.15, alpha: 1)
btn.layer.borderWidth = 1
btn.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
btn.layer.cornerRadius = 4
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
btn.addTarget(self, action: #selector(self.showLoggingTapped), for: .touchUpInside)
return btn
}()
// MARK: - Labels
let statusLabel: UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = "not started"
lbl.textColor = UIColor(white: 0.33, alpha: 1)
lbl.font = .systemFont(ofSize: 11)
return lbl
}()
let presetLabel: UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = ""
lbl.textColor = UIColor(white: 0.33, alpha: 1)
lbl.font = .systemFont(ofSize: 11)
return lbl
}()
override func viewDidLoad() {
super.viewDidLoad()
}
}