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)
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have the following lines of code to list some music titles from iTunes music. The code is 100% reproducible.
import SwiftUI
struct MusicView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
MusicListView(viewModel: viewModel)
}
}
struct MusicListView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
NavigationView {
List(viewModel.results, id: \.self) { result in
VStack(alignment: .leading) {
Text("Track ID: \(result.trackId)")
Text("Track name: \(result.trackName)")
}
}
.task {
do {
try await viewModel.fetchMusic()
} catch SessionError.badURL {
print("Bad URL")
} catch SessionError.invalidHTTPResponse {
print("Invalid HTTP response")
} catch SessionError.error(let err) {
print("Error: \(err)")
} catch {
print("\(error.localizedDescription)")
}
}
.navigationTitle("Music")
}
}
}
class ViewModel: ObservableObject {
@Published var results: [Result] = []
func fetchMusic() async throws {
guard let url = URL(string: "https://itunes.apple.com/search?term=classical+music&entity=song") else {
throw SessionError.badURL
}
let urlRequest = URLRequest(url: url, timeoutInterval: 0.00) // <<<<<<<<<<<<<
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
do {
guard let data = data, error == nil else {
throw SessionError.noData
}
guard let httpResponse = response as? HTTPURLResponse else {
throw SessionError.invalidHTTPResponse
}
switch httpResponse.statusCode {
case 200:
let res = try JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
self.results = res.results
}
case 400...499:
throw SessionError.badURL
default:
fatalError()
break
}
} catch {
print(error.localizedDescription)
}
}
.resume()
}
}
struct Response: Codable {
let resultCount: Int
let results: [Result]
}
struct Result: Codable, Hashable {
var trackId: Int
var trackName: String
var collectionName: String
}
enum SessionError: Error {
case badURL
case noData
case decoding
case invalidHTTPResponse
case badRequest(statusCode: Int)
case redirection(statusCode: Int)
case server(statusCode: Int)
case error(String)
}
As you see in the screenshot, I get some music titles listed.
My question is why I get a list when in fact I have the URLRequest's timeout value set to 0.00? I haven't run it with an actual device. As far as I use an iPhone simulator, regardless of the timeout value that I set, I get data downloaded. I wonder why?
Muchos thankos for reading
I have followed a tutorial written by Hacking with Swift ( https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui) about Core Data in SwiftUI. The Entity name is Student. And it has two properties: name (String), id (UUID). And the following is my code.
import SwiftUI
struct CoreView: View {
@Environment(\.managedObjectContext) var managedObject
@FetchRequest(sortDescriptors: []) var students: FetchedResults<Student>
var body: some View {
VStack {
List(students) { student in
Text(student.name ?? "Unknown")
}
Button {
let firstNames = ["Gary", "Harry", "Elane", "Ray", "Nancy", "Jim", "Susan"]
let lastNames = ["Johns", "McNamara", "Potter", "Thompson", "Hampton"]
if let selectedFirstName = firstNames.randomElement(), let selectedLastName = lastNames.randomElement() {
let newStudent = Student(context: managedObject)
newStudent.id = UUID()
newStudent.name = "\(selectedFirstName) \(selectedLastName)"
try? managedObject.save()
}
} label: {
Text("Add")
}
}
}
}
struct CoreView_Previews: PreviewProvider {
static var previews: some View {
CoreView()
.environmentObject(DataController())
}
}
If I list all records and then add a new student to the list, the app will insert the last addition at a random row. I wonder if I can order these records by the creation date?
Muchos thankos
Let me say that I have three structs that are sequentially connected.
ContentView -> FirstView -> SecondView
And I want to make a call from SecondView to ContentView with a button tap. So I have the following lines of code.
import SwiftUI
struct ContentView: View {
@State var goToFirst = false
var body: some View {
NavigationStack {
VStack {
NavigationLink {
FirstView(callBack: {
sayHello()
}, goToSecond: $goToFirst)
} label: {
Text("Go to First")
}
}
}
.navigationDestination(isPresented: $goToFirst) {
}
}
func sayHello() {
print("Hello!")
}
}
struct FirstView: View {
@State var callBack: (() -> Void)?
@Binding var goToSecond: Bool
var body: some View {
VStack {
Button("Go to Second") {
goToSecond.toggle()
}
}
.navigationDestination(isPresented: $goToSecond) {
SecondView(callBack: callBack)
}
}
}
struct SecondView: View {
@State var callBack: (() -> Void)?
var body: some View {
VStack {
Button("Tap me to make a call to ContentView") {
callBack?()
}
}
}
}
If I tap the button in SecondView, my ContentView will receive a call and call the sayHello function. Since ContentView and SecondView are not directly connected with each other, they have to through FirstView in this case. I wonder if there's a better or easier approach in having SecondView make a call to ContentView? In UIKit and Cocoa, you can make a delegate call to a distant class even when two classes are not directly connected with other. Using the notification is another option. In SwiftUI, I suppose you don't use either of them. Muchos thankos.
I have a sample macOS app that I'm working on. I can run the exactly same lines of code below for iOS. For now, I'm running code for macOS since I can just press Command + z to undo the last action.
Anyway, I have two Text View objects. Since TextView has the DragGesture gesture, I am able to freely move either of them. And I want to undo and redo their positions. So the following is what I have.
import SwiftUI
struct ContentView: View {
@State var textViews: [TextView] = [TextView(text: "George"), TextView(text: "Susan")]
var body: some View {
VStack {
ForEach(textViews, id: \.id) { textView in
textView
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct TextView: View {
@Environment(\.undoManager) var undoManager
@StateObject private var undoModel = UndoViewModel()
@State private var dragOffset: CGSize = .zero
@State private var position: CGSize = .zero
let id = UUID()
let text: String
init(text: String) {
self.text = text
}
var body: some View {
ZStack {
Text(text)
.fixedSize()
.padding(.vertical, 10)
.offset(x: dragOffset.width + position.width, y: dragOffset.height + position.height)
.gesture(
DragGesture()
.onChanged {
self.dragOffset = $0.translation
}
.onEnded( { (value) in
self.position.width += value.translation.width
self.position.height += value.translation.height
self.dragOffset = .zero
undoModel.registerUndo(CGSize(width: position.width, height: position.height), in: undoManager)
})
)
}
}
}
class UndoViewModel: ObservableObject {
@Published var point = CGSize.zero
func registerUndo(_ newValue: CGSize, in undoManager: UndoManager?) {
let oldValue = point
undoManager?.registerUndo(withTarget: self) { [weak undoManager] target in
target.point = oldValue // registers an undo operation to revert to old text
target.registerUndo(oldValue, in: undoManager) // this makes redo possible
}
undoManager?.setActionName("Move")
point = newValue // update the actual value
}
}
Well, if I press Command + z after moving one of them, it won't return to the last position. What am I doing wrong? Muchos thankos.
I'm trying to change the locale of an app with Picker as follows.
import SwiftUI
@main
struct LocaleSwitchCrazyMamaApp: App {
var body: some Scene {
WindowGroup {
let lanSetting = LanguageSetting()
ContentView()
.environmentObject(lanSetting)
.environment(\.locale, lanSetting.locale)
}
}
}
class LanguageSetting: ObservableObject {
@Published var locale = Locale(identifier: "en")
}
import SwiftUI
struct ContentView: View {
@State private var segmentSelection = 0
@EnvironmentObject var languageSetting: LanguageSetting
var body: some View {
VStack {
Text(NSLocalizedString("Hello", comment: ""))
.padding(.vertical, 20)
Picker("Language", selection: $segmentSelection) {
Text("English").tag(0)
Text("Japanese").tag(1)
Text("French").tag(2)
}
.frame(width: 200)
.pickerStyle(.segmented)
.onChange(of: segmentSelection) {newValue in
if newValue == 0 {
languageSetting.locale = Locale(identifier: "en")
} else if newValue == 1 {
languageSetting.locale = Locale(identifier: "ja")
} else {
languageSetting.locale = Locale(identifier: "fr")
}
}
}
.padding()
}
}
In addition, I have three locale versions like the following
"Hello" = "Hello"; // en.lproj
"Hello" = "Bonjour"; //fr.lproj
"Hello" = "こんにちは"; // ja.lproj
As long as I run the app on a simulator, the language of the Hello text won't change when tap any of the segments. What am I doing wrong?
Muchos thankos
I have a test app that is supposed to list a bunch of iTunes music records. I have existing lines of code that successfully load data. Now, I'm adding a loading state to them. As a result, I have the following lines code.
import SwiftUI
struct iTunesView: View {
@StateObject var viewModel = iTunesViewModel()
var body: some View {
switch viewModel.state {
case .idle: EmptyView()
case .loading: ProgressView()
case .loaded(let results):
List {
ForEach(results, id: \.self) { result in
Text("\(result.trackId)")
Text(result.trackName)
.lineLimit(0)
}
}
case .failed(let error):
Text(error.localizedDescription)
}
}
}
@MainActor
class iTunesViewModel: ObservableObject {
enum LoadingState {
case idle
case loading
case loaded([iTunesResult])
case failed(Error)
}
@Published var state: LoadingState = .idle
init() {
state = .loading
Task {
await fetchMusic()
}
}
func fetchMusic() async {
guard let url = URL(string: "https://itunes.apple.com/search?term=classical+music&entity=song") else {
state = .failed(URLError(.badURL))
return
}
do {
let urlRequest = URLRequest(url: url, timeoutInterval: 1.0)
let (data, _) = try await URLSession.shared.data(for: urlRequest)
let music = try JSONDecoder().decode(iTunesResponse.self, from: data)
self.state = .loaded(music.results)
print("\(music.results)")
} catch {
state = .failed(error)
}
}
}
struct iTunesResponse: Codable {
let resultCount: Int
let results: [iTunesResult]
}
struct iTunesResult: Codable, Hashable {
var trackId: Int
var trackName: String
var collectionName: String
}
enum iTunesError: Error {
case badURL
case decoding
case invalidHTTPResponse
case badData(statusCode: Int)
case badRequest(statusCode: Int)
case redirection(statusCode: Int)
case server(statusCode: Int)
case error(String)
}
For some reason, the app just shows a spinner although it reaches the print line in the fetchMusci function and print the string data. I wonder what I'm doing wrong? With the code lines above, an app is fully functional except it will show the progress guy, you know? I've made changes after reading this Stack overflow topic. Thanks.
I'm just playing with Combine, and I want to limit the number of letters that the text field can show. So I have the following lines of code.
import SwiftUI
struct ContentView: View {
@State var messageText: String = ""
var body: some View {
let bindingMessage = Binding {
messageText
} set: {
messageText = String($0.prefix(10))
print(String($0.prefix(10)))
}
Form {
TextField("Message", text: bindingMessage)
}
}
}
I'm expecting that the text field will show only the first 10 letters. But, as the screenshot below indicates, it shows the entire string. What am I doing wrong? Muchos Thankos. It sounds basic. Ugghhh...
I am perplexed as to how to use async await. In the following example, I don't use GCD or performSelector(inBackground:with:). The view controller is NSViewController, but it doesn't make any difference if it's NSViewController or UIViewController.
import Cocoa
class ViewController: NSViewController {
func startWriteImages() {
Task{
let bool = await startWriteImagesNext()
if bool {
print("I'm done!")
}
}
}
func startWriteImagesNext() async -> Bool {
// pictures is a path to a folder in the sandbox folder
// appDelegate.defaultFileManager is a variable pointing to FileManager.default in AppDelegate
let pictURL = URL(fileURLWithPath: pictures)
if let filePaths = try? self.appDelegate.defaultFileManager.contentsOfDirectory(atPath: pictURL.path) {
for file in filePaths {
let fileURL = pictURL.appending(component: file)
if self.appDelegate.defaultFileManager.fileExists(atPath: fileURL.path) {
let newURL = self.folderURL.appending(component: file)
do {
try self.appDelegate.defaultFileManager.copyItem(at: fileURL, to: newURL)
} catch {
print("Ugghhh...")
}
}
}
return true
}
return false
}
func startWriteImagesNext2() async -> Bool {
let pictURL = URL(fileURLWithPath: pictures)
if let filePaths = try? self.appDelegate.defaultFileManager.contentsOfDirectory(atPath: pictURL.path) {
DispatchQueue.global().async() {
for file in filePaths {
let fileURL = pictURL.appending(component: file)
if self.appDelegate.defaultFileManager.fileExists(atPath: fileURL.path) {
let newURL = self.folderURL.appending(component: file)
do {
try self.appDelegate.defaultFileManager.copyItem(at: fileURL, to: newURL)
} catch {
print("Ugghhh...")
}
}
}
}
return true
}
return false
}
}
In the code above, I'm saving each file in the folder to user-selected folder (self.folderURL). And the application will execute the print guy only when work is done. Since it's heavy-duty work, I want to use CCD or performSelector(inBackground:with:). If I use the former (startWriteImagesNext2), the application will execute the print guy right at the beginning. I suppose I cannot use GCD with async. So how can I perform heavy-duty work? Muchos thankos.
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)
}
}
}
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 macOS application developed in SwiftUI. It's a document-based application. I know how to hide the Show Tab Bar command under View. I don't want to hide it. I always want to show tabs. I wonder how to enable this command programmatically such that the document window always has the + button to the right. Thanks.
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've submitted my first AR app for iPhone and iPad to iTunes Connect. After sending a binary to iTunes Connect, I've received the following warning message.
The app contains the following UIRequiredDeviceCapabilities values, which aren’t supported in visionOS: [arkit].
No. 1, my app doesn't support visionOS. No. 2, I don't have the UIRequiredDeviceCapabilities dictionary in info.plist. Why am I receiving this warning? One article related to this issue that I've read suggests that I remove the UIRequiredDeviceCapabilities dictionary. Well, I don't have it in my plist. What can I do with this warning message? Thanks.
Topic:
Spatial Computing
SubTopic:
ARKit
A few weeks ago, I explored the possibility of installing auto-renewal subscription plans on an iOS app. Initially, I thought subscription plans are allowed for news and magazine apps. Then I saw this Apple website, which actually encourages us to provide 'free, limited access to app content.' So I took their advice and submitted an iOS app that has no dynamic content to App Store. I submitted another. Two apps have been approved. And I have got the third one put on hold several days ago. The reviewer has asked me an uncomfortable line of questions repeatedly like
What are changes?
How often?
that I have never received in my 13 or 14 year history. Then he or she rejected it two days ago. And I got a 4th one approved at the same time. So three are admitted in with one rejected.
Does an app have to have dynamic content to use auto-renewal subscription plans? I don't find any statement in Apple Review Guidelines that prohibits me from installing auto-renewal subscription plans on non-dynamic content app. There are other big-time apps like Microsoft 365 and Adobe Photoshop that are available with subscription plans. I am very perplexed.