A device running with the following lines of code can receive a message from a peripheral. In this manner, though, I can only receive messages from one peripheral since the service and characteristic IDs are hardcoded in CentralViewModel.swift. So my question is how I can observe messages from multiple peripherals. Thanks.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: CentralView()) {
Text("Central")
}
.buttonStyle(.borderedProminent)
.padding()
}
}
}
}
// CentralView.swift //
import SwiftUI
struct CentralView: View {
@StateObject var central: CentralViewModel = CentralViewModel()
var body: some View {
Text(central.message)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding(20)
.onDisappear {
central.stopAction()
}
}
}
// CentralViewModel.swift //
import Foundation
import CoreBluetooth
class CentralViewModel: NSObject, ObservableObject {
@Published var message: String = ""
var serviceUUID: CBUUID!
var characteristicUUID: CBUUID!
var centralManager: CBCentralManager!
var discoveredPeripheral: CBPeripheral?
var transferCharacteristic: CBCharacteristic?
var writeIterationsComplete = 0
//var connectionIterationsComplete = 0
let defaultIterations = 5
var data: Data = Data()
override init() {
super.init()
self.serviceUUID = CBUUID(string: "994F8A12-FE8E-4CCB-BD7B-1AE989A32853")
self.characteristicUUID = CBUUID(string: "F4BD0CA2-7581-40E2-A517-1CE275A3A749")
centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
}
func stopAction() {
centralManager.stopScan()
}
private func cleanup() {
guard let discoveredPeripheral = discoveredPeripheral, case .connected = discoveredPeripheral.state else { return }
for service in (discoveredPeripheral.services ?? [] as [CBService]) {
for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) {
if characteristic.uuid == characteristicUUID && characteristic.isNotifying {
self.discoveredPeripheral?.setNotifyValue(false, for: characteristic)
}
}
}
centralManager.cancelPeripheralConnection(discoveredPeripheral)
}
private func writeData() {
guard let discoveredPeripheral = discoveredPeripheral, let transferCharacteristic = transferCharacteristic
else {
return
}
while writeIterationsComplete < defaultIterations && discoveredPeripheral.canSendWriteWithoutResponse {
writeIterationsComplete += 1
}
if writeIterationsComplete == defaultIterations {
discoveredPeripheral.setNotifyValue(false, for: transferCharacteristic)
}
}
}
extension CentralViewModel: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("Power on")
startScanningForPeripherals()
return
case .poweredOff :
print("Power off")
return
case .resetting:
print("Resetting")
return
case .unauthorized:
print("Unauthorized")
return
case .unknown:
print("Unknown")
return
case .unsupported:
print("Unsupported")
return
@unknown default:
print("An unknown central manager state has occurred")
return
}
}
func startScanningForPeripherals() {
self.centralManager.scanForPeripherals(withServices: [self.serviceUUID], options: nil)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard RSSI.intValue >= -50 else {
return
}
if discoveredPeripheral != peripheral {
print("Peripheral discovered")
discoveredPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
print("Service discovered")
}
}
extension CentralViewModel: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
cleanup()
return
}
guard let peripheralServices = peripheral.services else {
return
}
for service in peripheralServices {
peripheral.discoverCharacteristics([characteristicUUID], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
cleanup()
return
}
guard let serviceCharacteristics = service.characteristics else {
return
}
for characteristic in serviceCharacteristics where characteristic.uuid == characteristicUUID {
transferCharacteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Error changing notification state: \(error.localizedDescription)")
return
}
guard characteristic.uuid == characteristicUUID else {
return
}
if characteristic.isNotifying {
print("Notification began on \(characteristic)")
} else {
print("Notification stopped on \(characteristic). Disconnecting")
cleanup()
}
}
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
print("Peripheral is ready to send data to YOU!")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
cleanup()
return
}
guard let characteristicData = characteristic.value,
let stringFromData = String(data: characteristicData, encoding: .utf8) else {
return
}
print("Received \(characteristicData.count) bytes: \(stringFromData)")
if stringFromData == "EOM" {
message = String(data: data, encoding: .utf8) ?? ""
writeData()
} else {
data.append(characteristicData)
}
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm showing a Text View when a button with an image is long-pressed.
import SwiftUI
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var isDark: Bool {
return colorScheme == .dark
}
@State private var showLabel = false
var body: some View {
Button(action: {
}) {
VStack {
ZStack {
Image(systemName: "swift")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 32)
.padding(.horizontal, 40)
.padding(.vertical, 6)
.background(.gray.opacity(0.2), in: RoundedRectangle(cornerRadius: 10))
.onTapGesture {
showLabel.toggle()
}
.onLongPressGesture(minimumDuration: 2) {
print("Long pressed...")
showLabel.toggle()
}
if showLabel {
Text("Help Content")
.font(.caption)
.foregroundStyle(!isDark ? .white : .black)
.padding(10)
.background(!isDark ? .black : .white, in: Rectangle())
.onTapGesture {
print("hey")
showLabel.toggle()
}
.offset(x: 120)
}
}
}
}
}
}
So a Text View will appear as shown in the image above. But its .onTapGesture is never called. I wonder why? Thanks.
I'm using NSTableView with NSViewRepresentable in my SwiftUI ContentView. I'm letting the user right-click on a table row such that the application will recognize the row number, which is achieved. The following is what I have so far.
struct ContentView: View {
@State private var rowSelection = -1
VStack {
TableView(tableData: someData, selectedRow: $rowSelection)
}
}
struct TableView: NSViewRepresentable {
@Binding var tableData: [Dictionary<String, String>]
@Binding var selectedRow: Int
func makeNSView(context: Context) -> NSScrollView {
let scrollView = NSScrollView(frame: .zero)
let tableView = NSTableView()
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
let contextMenu = NSMenu()
let copyRowMenuItem = NSMenuItem(title: "Copy Row", action: #selector(Coordinator.tableRowAction(_:)), keyEquivalent: "")
contextMenu.addItem(copyRowMenuItem)
copyRowMenuItem.target = context.coordinator
tableView.menu = contextMenu
scrollView.documentView = tableView
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = true
scrollView.autohidesScrollers = true
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(tableData: $tableData, tableInfo: $tableInfo, selectedRow: $selectedRow, rowSelected: $rowSelected)
}
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
@Binding var tableData: [Dictionary<String, String>]
@Binding var selectedRow: Int
init(tableData: Binding<[Dictionary<String, String>]>, tableInfo: Binding<[PragmaModel]>, selectedRow: Binding<Int>, rowSelected: Binding<Bool>) {
self._tableData = tableData
self._rowSelected = rowSelected
}
func numberOfRows(in tableView: NSTableView) -> Int {
return tableData.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
for i in 0..<tableInfo.count {
let col = NSString(format: "%i", i) as String
let identifier = NSString(format: "Column%i", i) as String
if ((tableColumn?.identifier)!.rawValue == identifier) {
let data = tableData[row][col]
return data
}
}
return nil
}
func tableViewSelectionDidChange(_ notification: Notification) {
let tv = notification.object as! NSTableView
if tv.selectedRow >= 0 {
selectedRow = tv.selectedRow
}
}
@objc func tableRowAction(_ sender: Any) {
// closure //
}
}
}
The contextual menu works. Yet, the application needs to know when a row is clicked on. So I want to send a closure back to ContentView. How can I do that, por favor? Muchos thankos.
I'm trying to set the background color of a button with label like the following for a macOS application. I haven't run it for iOS.
VStack(spacing: 20) {
HStack(spacing: 32) {
Button {
showGuide.toggle()
} label: {
Text("Hello")
.font(.title3)
.frame(width: 190, height: 36)
}
.foregroundStyle(.primary)
.background(.yellow)
.clipShape(.capsule)
.shadow(color: .red, radius: 8)
Button {
} label: {
Text("Good morning")
.font(.title3)
.frame(width: 190, height: 36)
}
.foregroundStyle(.primary)
.background(.pink)
.clipShape(.capsule)
.shadow(color: .red, radius: 8)
}
}
.frame(maxWidth: .infinity, alignment: .center)
Interestingly, those two buttons have a white background color under the light appearance as shown below.
And it will get the designated background color under the dark appearance as shown below.
So why don't I get the buttons colored under the light appearance? I can't figure out why it happens. Does anybody know why? Thanks.
Using List to list an array of an object isn't a problem. For example, I have simple lines of code below to list an array of some guy.
struct ContentView: View {
@State private var selectedFieldItem: FieldItem?
private var fieldListView: some View {
List(selection: $selectedFieldItem) {
ForEach(fieldItems.indices, id: \.self) { index in
Button {
...
} label: {
let fieldItem = fieldItems[index]
HStack(spacing: 10) {
Text("\(fieldItem.name)")
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.buttonStyle(.borderless)
}
.onMove(perform: fieldlocate)
}
.listStyle(.plain)
}
private func fieldlocate(from source: IndexSet, to destination: Int) {
fieldItems.move(fromOffsets: source, toOffset: destination)
}
}
As you see in the picture below, I can move a row up and down.
A problem that I now have with List is that I cannot know how those rows are rearranged after one of them is moved up or down. In Cocoa, you can tell the positions of all rows after one moves with
tableView(_:acceptDrop:row:dropOperation:)
I think I have done the same in UIKit. Can we tell the current row numbers of List items in SwiftUI? Like
Item 0 moves from Row 0 to Row 2
item 1 moves from Row 1 to Row 0
item 2 moves from Row 2 to Row 1
after one drags Item 0 from the top to the bottom?
Muchos thankos.
Topic:
UI Frameworks
SubTopic:
SwiftUI
I see a lot of tutorials that show how to open a SwiftUI View when a NSStatusItem is clicked on. That's not what I want. I need to show a SwiftUI View when I click on a button over SwiftUI View.
So far the following is what I have.
import SwiftUI
@main
struct MyStatusApp_App: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
#if os(macOS)
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem!
private var popover: NSPopover?
func applicationDidFinishLaunching(_ notification: Notification) {
hideTitleBar()
NSApp.setActivationPolicy(.accessory)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem.button {
if let image = NSImage(named: "statusImage") {
button.image = image
}
}
}
#endif
// ContentView //
import SwiftUI
struct ContentView: View {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
VStack {
Button("Click me!") {
let popOver = NSPopover()
popOver.contentViewController = NSHostingController(rootView: NotificationView())
appDelegate.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let statusBarbutton = appDelegate.statusItem.button {
popOver.show(relativeTo: statusBarbutton.bounds, of: statusBarbutton, preferredEdge: .minY)
}
}
}
.frame(width: 200, height: 100)
}
}
If I run the application and click on the button (orange arrow) over ContentView, a guy from NotificationView will appear (green rectangle). That's good. But it appears not below the status item (red arrow). It's positioned at an odd location. It's way below the status item guy. What am I doing wrong? Muchos thankos.
I guess site's add image function is broken. It doesn't show my screenshot.

I have a very cheap Bluetooth-connected printer. And I want to print out a word or two via Core Bluetooth. It's an iOS app with the SwiftUI framework. The following is what I have for an ObservableObject class.
import Foundation
import CoreBluetooth
class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
@Published var connectedDevices: [CBPeripheral] = []
@Published var powerOn = false
@Published var peripheralConnected = false
private var centralManager: CBCentralManager!
private var peripheralName = "LX-D02"
private var connectedPeripheral: CBPeripheral?
private var writeCharacteristic: CBCharacteristic?
private let serviceUUID = CBUUID(string:"5833FF01-9B8B-5191-6142-22A4536EF123")
private let characteristicUUID = CBUUID(string: "FFE1")
override init() {
super.init()
self.centralManager = CBCentralManager(delegate: self, queue: nil)
}
func startScanning() {
if centralManager.state == .poweredOn {
centralManager.scanForPeripherals(withServices: nil, options: nil)
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
powerOn = true
print("Bluetooth is powered on")
} else {
print("Bluetooth is not available")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !connectedDevices.contains(peripheral) {
if let localName = advertisementData["kCBAdvDataLocalName"] as? String {
if localName == peripheralName {
connectedDevices.append(peripheral)
centralManager.connect(peripheral, options: nil)
centralManager.stopScan()
peripheralConnected = true
print("Connected: \(peripheral.identifier.uuidString)")
}
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
connectedPeripheral = peripheral
peripheral.delegate = self
let services = [serviceUUID]
peripheral.discoverServices(services)
//discoverServices(peripheral: peripheral)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) {
guard let error = error else {
print("Failed connection unobserved")
return
}
print("Error: \(error.localizedDescription)")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let error = error {
print("Failing to discover servies: \(error.localizedDescription)")
return
}
discoverCharacteristics(peripheral: peripheral)
}
/* Return all available services */
private func discoverServices(peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
private func discoverCharacteristics(peripheral: CBPeripheral) {
guard let services = peripheral.services else {
return
}
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
let characteristicUUID = characteristic.uuid
print("Discovered characteristic: \(characteristicUUID)")
peripheral.setNotifyValue(true, for: characteristic)
if characteristic.properties.contains(.writeWithoutResponse) {
writeCharacteristic = characteristic
print("You can write!!!") // Never read...
}
if characteristic.properties.contains(.write) {
print("You can write?")
writeCharacteristic = characteristic // Being read...
}
}
func writeToPrinter() {
guard let peripheral = connectedPeripheral else {
print("Ughhh...")
return
}
if let characteristic = writeCharacteristic {
if let data = "Hello".data(using: .utf8, allowLossyConversion: true) {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
peripheral.writeValue(data, for: characteristic, type: .withResponse) // -> Message sent successfully
}
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Writing error: \(error.localizedDescription)")
return
}
print("Message sent successfully")
}
}
My app has no trouble connecting to the bluetooth-connected printer. Initially, I called
discoverServices(peripheral:)
to get all services And I get a service identifier (5833FF01-9B8B-5191-6142-22A4536EF123) for my printer. peripheral(_:didDiscoverCharacteristicsFor:error:) doesn't return a thing for .writeWithoutResponse but does return a characteristic for .write. Eventually, if I call writeToPrinter(),
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
returns
WARNING: Characteristic <CBCharacteristic: 0x3019040c0, UUID = 5833FF02-9B8B-5191-6142-22A4536EF123, properties = 0x8, value = (null), notifying = NO> does not specify the "Write Without Response" property - ignoring response-less write
If I call
peripheral.writeValue(data, for: characteristic, type: .withResponse)
, there is no error. But I get no output from the printer. What am I doing wrong? Thanks.
I have a simple SwiftUI project with two basic build configurations (Debug, Release) as shown below.
I now choose Build > Scheme > Edit Scheme under Product and select Release as the current build configuration as shown below.
And the Preview canvas exhibit errors.
If I click on the Diagnostics button, it says under PREVIEW UPDATE ERROR
OptimizationLevelError: not building -Onone
”BuildSchemeCrazyDaughter.app” needs -Onone Swift optimization level to use previews (current setting is -O)
What does that mean and why don't I get the preview for the Release build configuration? Thanks.
I have a sample document-based macOS app. I understand that you can open a new window or a new tab with some text.
import SwiftUI
struct ContentView: View {
@Binding var document: TexDocument
@Environment(\.newDocument) var newDocument
var body: some View {
VStack(spacing: 0) {
topView
}
}
private var topView: some View {
Button {
newDocument(TexDocument(text: "A whole new world!"))
} label: {
Text("Open new window")
.frame(width: 200)
}
}
}
Suppose that I have a path to a text file whose security-scoped bookmark can be resolved with a click of a button. I wonder if you can open a new window or a new tab with the corresponding content?. I have done that in Cocoa. I hope I can do it in SwiftUI as well. Thanks.
I have the following lines of code to access data through CoreData.
import Foundation
import CoreData
import CloudKit
class CoreDataManager {
static let instance = CoreDataManager()
let container: NSPersistentCloudKitContainer
let context: NSManagedObjectContext
init() {
container = NSPersistentCloudKitContainer(name: "ABC")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print(error.userInfo)
}
})
context = container.viewContext
context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
}
func save() {
do {
try container.viewContext.save()
print("Saved successfully")
} catch {
print("Error in saving data: \(error.localizedDescription)")
}
}
}
I have confirmed that I can share data between iPhone and iPad. Now, I need to use AppGroup as well. I have changed my code as follows.
import Foundation
import CoreData
import CloudKit
class CoreDataManager {
static let shared = CoreDataManager()
let container: NSPersistentContainer
let context: NSManagedObjectContext
init() {
container = NSPersistentCloudKitContainer(name: "ABC")
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "some group name")!.appendingPathComponent("CoreDataMama.sqlite"))]
container.loadPersistentStores(completionHandler: { (description, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
})
context = container.viewContext
context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
}
func save() {
do {
try container.viewContext.save()
print("Saved successfully")
} catch {
print("Error in saving data: \(error.localizedDescription)")
}
}
}
Other files being unaltered, my sample apps aren't sharing data. What am I doing wrong? Just FYI, I'm using actual devices. Thank you for your reading this topic.
The other day I was playing with iBeacon and found out that CLBeaconIdentityConstraint will be deprecated after iOS 18.5. So I've written code with BeaconIdentityCondition in reference to this Apple's sample project.
import Foundation
import CoreLocation
let monitorName = "BeaconMonitor"
@MainActor
public class BeaconViewModel: ObservableObject {
private let manager: CLLocationManager
static let shared = BeaconViewModel()
public var monitor: CLMonitor?
@Published var UIRows: [String: [CLMonitor.Event]] = [:]
init() {
self.manager = CLLocationManager()
self.manager.requestWhenInUseAuthorization()
}
func startMonitoringConditions() {
Task {
print("Set up monitor")
monitor = await CLMonitor(monitorName)
await monitor!.add(getBeaconIdentityCondition(), identifier: "TestBeacon")
for identifier in await monitor!.identifiers {
guard let lastEvent = await monitor!.record(for: identifier)?.lastEvent else { continue }
UIRows[identifier] = [lastEvent]
}
for try await event in await monitor!.events {
guard let lastEvent = await monitor!.record(for: event.identifier)?.lastEvent else { continue }
if event.state == lastEvent.state {
continue
}
UIRows[event.identifier] = [event]
UIRows[event.identifier]?.append(lastEvent)
}
}
}
func updateRecords() async {
UIRows = [:]
for identifier in await monitor?.identifiers ?? [] {
guard let lastEvent = await monitor!.record(for: identifier)?.lastEvent else { continue }
UIRows[identifier] = [lastEvent]
}
}
func getBeaconIdentityCondition() -> CLMonitor.BeaconIdentityCondition {
CLMonitor.BeaconIdentityCondition(uuid: UUID(uuidString: "abc")!, major: 123, minor: 789)
}
}
It works except that my sample app can take as long as 90 seconds to see event changes. You would get an instant update with an fashion (CLBeacon and CLBeaconIdentityConstraint). Is there anything that I can do to see changes faster? Thanks.
I'm trying to animate a shape. The following is my code.
import SwiftUI
struct ContentView7: View {
@State private var goingLeft = true
@State private var goingUp = true
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
CustomShape(quadDistance: goingUp ? 0: 60.0, horizontalDeviation: 0.0)
.fill(Color.red)
.ignoresSafeArea()
.animation(.easeInOut(duration: 1.0).repeatForever(), value: goingUp)
}.onAppear {
goingUp = false
}
}
}
struct CustomShape: Shape {
var quadDistance: CGFloat
var horizontalDeviation: CGFloat
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: -horizontalDeviation, y: 0))
path.addLine(to: CGPoint(x: rect.maxX + horizontalDeviation , y: 0))
path.addLine(to: CGPoint(x: rect.maxX + horizontalDeviation, y: rect.midY))
path.addLine(to: CGPoint(x: -horizontalDeviation, y: rect.midY))
path.move(to: CGPoint(x: -horizontalDeviation, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: rect.maxX + horizontalDeviation, y: rect.midY), control: CGPoint(x: rect.midX, y: rect.midY + quadDistance))
}
}
}
Well, my intension is to move the center of the convex point up and down. But the shape won't animate itself. What am I doing wrong? Muchos thankos.
I am just playing with NSTextList by creating a sample iOS app. The following is my code.
import UIKit
class ViewController: UIViewController {
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = ""
textView.contentInsetAdjustmentBehavior = .automatic
textView.backgroundColor = .white
textView.font = UIFont.systemFont(ofSize: 20.0)
textView.textColor = .black
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.widthAnchor.constraint(equalToConstant: 600.0),
textView.heightAnchor.constraint(equalToConstant: 600.0)
])
return textView
}()
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("End list", for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitleColor(.lightGray, for: .highlighted)
button.backgroundColor = .black
button.layer.cornerRadius = 8.0
button.addTarget(self, action: #selector(fixTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 100.0),
button.heightAnchor.constraint(equalToConstant: 42.0)
])
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(textView)
view.addSubview(button)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tapGesture)
NSLayoutConstraint.activate([
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20.0)
])
let list = NSTextList(markerFormat: .diamond, options: 0)
list.startingItemNumber = 1
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.textLists = [list]
let attributes = [NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24.0)]
let attributedStr = NSMutableAttributedString(string: "\n\n\n\n\n", attributes: attributes)
textView.textStorage.setAttributedString(attributedStr)
}
@objc func fixTapped() {
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
}
When the app launches itself, I get 5 lines of diamond guys as shown in the following screenshot.
If I keep pressing the delete key with a connected keyboard, the list will be gone as shown below.
But if I press the RETURN key several times, the diamond list will come back as shown below.
So how can I end this June TextList madness? In code, I have the dismissKeyboard function if I can end this madness programmatically.
Thanks,
Señor Tomato Spaghetti
Chief Janitor at
Southeastern Tomato Spaghetti Trade Association
I've been trying to save a selected color with UserDefaults from UIColorPickerViewController. But I run into a color space fiasco. Anyway, here come my lines of code.
class ViewController: UIViewController, UIColorPickerViewControllerDelegate {
@IBOutlet weak var imageView: UIImageView!
@IBAction func selectTapped(_ sender: UIButton) {
let picker = UIColorPickerViewController()
picker.delegate = self
picker.selectedColor = .yellow
picker.supportsAlpha = false
present(picker, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let color = UserDefaultsUIColor.shared.readColor(key: "MyColor") {
print("Color being read: \(color)")
}
}
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
let color = viewController.selectedColor
print("Selected color: \(color)")
UserDefaultsUIColor.shared.saveColor(color: viewController.selectedColor, key: "MyColor")
}
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
imageView.backgroundColor = viewController.selectedColor
}
}
class UserDefaultsUIColor {
static let shared = UserDefaultsUIColor()
func saveColor(color: UIColor, key: String) {
let userDefaults = UserDefaults.standard
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false) as NSData?
userDefaults.set(data, forKey: key)
} catch {
print("Error UserDefaults: \(error.localizedDescription)")
}
}
func readColor(key: String) -> UIColor? {
let userDefaults = UserDefaults.standard
if let data = userDefaults.data(forKey: key) {
do {
if let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) {
return color
}
} catch {
print("Error UserDefaults")
}
}
return nil
}
}
I first start out with a yellow color (UIColor.yellow). And I select a color whose RGB values are 76, 212, 158, respectively. And the color picker guy returns the following.
kCGColorSpaceModelRGB 0.298039 0.831373 0.619608 1
And I get the following in reading the saved color data object.
UIExtendedSRGBColorSpace -0.270778 0.84506 0.603229 1
How can I save and read color data objects consistently? I could specify a color space when I save a color. But it doesn't go well.
Muchos thankos
Señor Tomato de Source
Oh, boy... Xcode has become more and more difficult to deal with. Today, I've dowloaded Version 15.0 beta 4. It took my 2019 iMac with 64 GB of RAM some 20 minutes just to launch an iPhone 14 Simulator and to let me see the home screen. Xcode takes 3 or 4 minutes to run code after I change just one line. I only have some 30 lines of code in total. It's a truly disappointing update. I wish they stop adding unnecessary features like code-folding animation to slow things down.
import UIKit
class ViewController: UIViewController {
private let photoView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "airplane")
//imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
view.addSubview(photoView)
NSLayoutConstraint.activate([
photoView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
photoView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
photoView.widthAnchor.constraint(equalToConstant: 200),
photoView.heightAnchor.constraint(equalToConstant: 200)
])
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.runAirplaneAnimation()
}
}
func runAirplaneAnimation() {
photoView.addSymbolEffect(.pulse, animated: true)
}
}