Posts under App & System Services topic

Post

Replies

Boosts

Views

Activity

StoreKit Subscription Fails to Load During App Review, Works in Sandbox/TestFlight
Hi everyone, I’m facing a recurring issue with my macOS app being rejected during App Store review, and I’d really appreciate any guidance. The subscription flow in my app is implemented using StoreKit, and everything works perfectly in our development environment using a StoreKit configuration file. It also behaves as expected in Sandbox testing and TestFlight — I even had few beta testers confirm that the subscription information is displayed correctly and the purchase flow completes without issues. All required subscription details are configured in App Store Connect: • Subscription duration and the description of the services offered • Price and price per unit where applicable • Paid apps agreement and related forms are correctly filled However, when the app is submitted for review, the subscription screen fails to display the expected information. From what I can tell, the product information fails to load from the App Store in the review environment — even though everything is working fine on our side. We’ve already submitted a video to Apple showing the subscription UI working in the Sandbox environment, but the app continues to be rejected under guideline 3.1.2 due to missing subscription info in the binary. Is anyone else experiencing similar behavior during review? Could there be a caching issue or delay in StoreKit syncing for newly configured products? Any help or suggestions are very welcome. Thanks in advance!
0
0
111
Apr ’25
Apple Pay - PKAddPaymentPassViewController doesn't show correct list of devices
Hi, We are trying to make the PKAddPaymentPassViewController to show the correct list of devices to where the pass can be added. We have analysed the documentation and we are using the PrimaryAccountIdentifier field which is the field that supposedly controls this behavior but the list of devices presented in the view controller always include one iPhone and one Apple Watch, regardless of where the card has been already added. We are initializing the PKAddPaymentPassRequestConfiguration object with: PKEncryptionScheme PrimaryAccountIdentifier CardholderName PrimaryAccountSuffix LocalizedDescription PaymentNetwork PrimaryAccountIdentifier CardholderName PrimaryAccountSuffix LocalizedDescription We have also verified the configuration in our payment pass processor and everything should be ok. We would like to have some help on achieving the desired flow for Apple Pay, which is to present the PKAddPaymentPassViewController with the correct list of available devices and not the full list. Thank you.
1
0
576
Apr ’25
What does iOS do wrt Shared Web Credentials when it makes a call to a server to perform a message filter request
In order to create a Message Filter Extension it is necessary to set up Shared Web Credentials. I'd like to form an understanding of what role SWC plays when the OS is making request to the associated network service (when the extension has called deferQueryRequestToNetwork()) and how this differs from when an app directly uses Shared Web Credentials itself. When an app is making direct use of SWC, it makes a request to obtain the user's credentials from the web site. However in the case of a Message Filter Extension, there aren't any individual user credentials, so what is happening behind the scenes when the OS makes a server request on behalf of a Message Filtering Extension? A more general question - the documentation for Shared Web Credentials says "Associated domains establish a secure association between domains and your app.". Thank you
2
0
473
Apr ’25
Apple Pay integration with JS
I am facing an issue with Apple Pay js while doing the integration we are using reference https://applepaydemo.apple.com/apple-pay-js-api In this I can generate the merchantSession correctly But when I pass that merchantSession in session.completeMerchantValidation(merchantValidation) as per documentation It is getting failed and also no appropriate error is being shown in the console
0
0
139
Apr ’25
Weatherkit - visibility units and height
reposting this in case it got missed the first time around here https://developer.apple.com/forums/thread/775900 We had a question that came up when we comparing data from WeatherKit to other sources - WeatherKit visibility was well beyond the boundaries we had historically, even from Darksky. That raises two questions: is visibility actually in meters like the docs say? is this visibility at ground level, 500ft, or some other height? We were seeing visibility numbers of up to 40 miles (after converting the number the API sent to miles), where all of our other sources are usually within 10 miles
0
0
70
Apr ’25
Custom IPSec IKEv2 with Packet Tunnel Provider Extension on iOS
We’re looking to implement a custom IPSec IKEv2 VPN using the Packet Tunnel Provider network extension on iOS because we need to add extra information to EAP, which the built-in IKEv2 VPN configuration does not support. Is it possible to handle the full IKEv2 negotiation and IPSec tunneling within the Packet Tunnel Provider extension? Or are there limitations that would prevent implementing a full IKEv2 stack this way? Any insights or alternative approaches would be appreciated. Thanks!
1
0
108
Mar ’25
The Apple Pay interface is not responding
My server's access to Apple's payment interface (buy. itunes. apple. com/verifiyReceipt) has been unresponsive since the end of March, and I have been searching for a long time without finding any issues. Normally, even if the data is incorrect, there is still a {"status": 21000} response. We are using Alibaba Cloud's virtual servers here. I don't know if Apple has made any adjustments to the interface. If anyone has encountered this problem, please kindly help to answer it. Thank you all.
0
0
69
Apr ’25
NotSupportedError, The payment method is not supported
What am I missing in my checking for whether or not to offer Apple Pay on my website? <script async crossorigin src="https://applepay.cdn-apple.com/jsapi/v1.1.0/apple-pay-sdk.js" ></script> ... <style> apple-pay-button { display: none; } </style> ... <apple-pay-button buttonstyle="black" type="plain" locale="en-US" onclick="startApplePay('${APPLE_PAY_MERCHANT_ID}','${paymentForm.amount}');"></apple-pay-button> So, the button is not displayed by default. I only change the style to displayed if: window.onload = function() { if (isApplePaySupported()) { document.querySelector("apple-pay-button").style.display = "inline-block"; }; } function isApplePaySupported() { return (window.PaymentRequest && window.ApplePaySession && ApplePaySession.canMakePayments() && ApplePaySession.supportsVersion(applePayVersion)); } Yet, once in a while a click comes through that tries to create a PaymentRequest with const applePayMethod = { "supportedMethods": "https://apple.com/apple-pay", "data": { "version": applePayVersion, "merchantIdentifier": merchantIdentifier, "merchantCapabilities": [ "supports3DS" ], "supportedNetworks": [ "amex", "discover", "masterCard", "visa" ], "countryCode": "US" } }; and results in: NotSupportedError, The payment method is not supported What else might be "not supported" in the request for this particular user/device/wallet? In particular, that could be known immediately when the PaymentRequest is created, but before any payment instrument from the wallet is selected? And, is there anything I could detect before showing the button? Or, is it even possible for the button to be clicked by some kind of automation, even if it's not displayed?
1
0
100
Apr ’25
Live Activity Start Token not generating after certain days of usage for non Production builds
Live Activity Start Token not generating after certain days of usage. We have implemented Live Activity feature where the initial activity is launched by our backend. But to start that first live activity I need push to start token which is generating for few days but all of sudden after certain days it stops generating. Currently we are in development phase so we test it on multiple devices and multiple time we are doing install and uninstall. STEPS TO REPRODUCE Install the app Start token gets generated which is sent to our server After certain duration server sends the first live activity using that token user opens the app then we receive the updated token and send that token to server server uses that updated token to further update the live activity. All this works fine. But after a week of usage we are observing that we stop getting start token from APNS. Not sure where exactly the thing is breaking. We have tried with different devices and different bundle identifiers but behaviour is same for all. func generateStartToken() { Task.detached { [weak self] in guard let self else { return } await self.observeActivityPushTokenAndState() for await data in ActivityKit.Activity<LiveActivityAttribute>.pushToStartTokenUpdates { let token = data.map { String(format: "%02x", $0) }.joined() print("Activity Start token: ", token) } } } func observeActivityPushTokenAndState() { Task.detached { for await activity in ActivityKit.Activity<LiveActivityAttribute>.activityUpdates { Task { for await tokenData in activity.pushTokenUpdates { let updatedToken = tokenData.map { String(format: "%02x", $0) }.joined() print("Activity Update token: ", updatedToken) } } Task { for await content in activity.contentUpdates { let updatedContent = content.state print("Activity Updated: ", updatedContent) } } } } }
2
0
188
Apr ’25
Using any SwiftData Query causes app to hang
I want to get to a point where I can use a small view with a query for my SwiftData model like this: @Query private var currentTrainingCycle: [TrainingCycle] init(/*currentDate: Date*/) { _currentTrainingCycle = Query(filter: #Predicate<TrainingCycle> { $0.numberOfDays > 0 // $0.startDate < currentDate && currentDate < $0.endDate }, sort: \.startDate) } The commented code is where I want to go. In this instance, it'd be created as a lazy var in a viewModel to have it stable (and not constantly re-creating the view). Since it was not working, I thought I could check the same view with a query that does not require any dynamic input. In this case, the numberOfDays never changes after instantiation. But still, each time the app tries to create this view, the app becomes unresponsive, the CPU usage goes at 196%, memory goes way high and the device heats up quickly. Am I holding it wrong? How can I have a dynamic predicate on a View in SwiftUI with SwiftData?
2
0
238
Mar ’25
Combining Screen Time Usage with HealthKit Data in a Chart
Hello Apple Developer Community, I’m working on creating a chart that combines Screen Time Usage data with Workout Time from HealthKit. I’ve successfully implemented a DeviceActivityReportExtension to fetch Screen Time data and draw a chart. I’m also able to read HealthKit data from the main app. However, I’m having trouble integrating the HealthKit data into the View generated by the DeviceActivityReportExtension. I’ve attempted to read HealthKit data directly from the extension , but this doesn’t seem to work, likely due to HealthKit access restrictions in extensions. I also tied using a shared object to pass HealthKit data to the extension, but unfortunately this didn’t seem to work as expected. I’d greatly appreciate any suggestions on how to successfully integrate HealthKit data into the extension-generated View. Has anyone dealt with a similar challenge or found a workaround for this? Thanks in advance for your help!
0
0
103
Mar ’25
Issue Integrating Apple Pay JS – `merchantSession` Blocke
Hello Apple Devs, We’re currently trying to integrate Apple Pay on the web using Apple Pay JS. We've followed the official documentation closely, but we're running into a blocker during the merchantSession validation phase. We successfully retrieved a merchantSession, which looks like this: json { "displayName": "Our Name", "domainName": "https://pay.ourdomain.co", "epochTimestamp": , "expiresAt": ****************, "merchantIdentifier": "", "merchantSessionIdentifier": ", "nonce": "", "operationalAnalyticsIdentifier": our name "t:", "pspId": "", "retries": 0, "signature": "*****************..." } Issue: Shortly after initiating the session, we receive a cancel event with the following info: ApplePayCancelEvent { type: "cancel", sessionError: { code: "unknown", info: {} } } We're unsure what causes the cancellation. There are no clear error messages or hints in the logs to identify what went wrong. What We’ve Checked: The merchantSession is returned successfully from our backend. The domainName matches our frontend domain (https://pay.durdomain.co). The session hasn’t expired when tested. We're using Apple Pay JS APIs as described in the documentation. Help Needed: What can trigger an ApplePayCancelEvent with an "unknown" error code? Any insight or guidance would be deeply appreciated. Thanks in advance!
1
0
132
Apr ’25
Moving from Multipeer Connectivity to Network Framework
I see a lot of folks spend a lot of time trying to get Multipeer Connectivity to work for them. My experience is that the final result is often unsatisfactory. Instead, my medium-to-long term recommendation is to use Network framework instead. This post explains how you might move from Multipeer Connectivity to Network framework. If you have questions or comments, put them in a new thread. Place it in the App & System Services > Networking topic area and tag it with Multipeer Connectivity and Network framework. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Moving from Multipeer Connectivity to Network Framework Multipeer Connectivity has a number of drawbacks: It has an opinionated networking model, where every participant in a session is a symmetric peer. Many apps work better with the traditional client/server model. It offers good latency but poor throughput. It doesn’t support flow control, aka back pressure, which severely constrains its utility for general-purpose networking. It includes a number of UI components that are effectively obsolete. It hasn’t evolved in recent years. For example, it relies on NSStream, which has been scheduled for deprecation as far as networking is concerned. It always enables peer-to-peer Wi-Fi, something that’s not required for many apps and can impact the performance of the network (see Enable peer-to-peer Wi-Fi, below, for more about this). Its security model requires the use of PKI — public key infrastructure, that is, digital identities and certificates — which are tricky to deploy in a peer-to-peer environment. It has some gnarly bugs. IMPORTANT Many folks use Multipeer Connectivity because they think it’s the only way to use peer-to-peer Wi-Fi. That’s not the case. Network framework has opt-in peer-to-peer Wi-Fi support. See Enable peer-to-peer Wi-Fi, below. If Multipeer Connectivity is not working well for you, consider moving to Network framework. This post explains how to do that in 13 easy steps (-: Plan for security Select a network architecture Create a peer identifier Choose a protocol to match your send mode Discover peers Design for privacy Configure your connections Manage a listener Manage a connection Send and receive reliable messages Send and receive best effort messages Start a stream Send a resource Finally, at the end of the post you’ll find two appendices: Final notes contains some general hints and tips. Symbol cross reference maps symbols in the Multipeer Connectivity framework to sections of this post. Consult it if you’re not sure where to start with a specific Multipeer Connectivity construct. Plan for security The first thing you need to think about is security. Multipeer Connectivity offers three security models, expressed as choices in the MCEncryptionPreference enum: .none for no security .optional for optional security .required for required security For required security each peer must have a digital identity. Optional security is largely pointless. It’s more complex than no security but doesn’t yield any benefits. So, in this post we’ll focus on the no security and required security models. Your security choice affects the network protocols you can use: QUIC is always secure. WebSocket, TCP, and UDP can be used with and without TLS security. QUIC security only supports PKI. TLS security supports both TLS-PKI and pre-shared key (PSK). You might find that TLS-PSK is easier to deploy in a peer-to-peer environment. To configure the security of the QUIC protocol: func quicParameters() -> NWParameters { let quic = NWProtocolQUIC.Options(alpn: ["MyAPLN"]) let sec = quic.securityProtocolOptions … configure `sec` here … return NWParameters(quic: quic) } To enable TLS over TCP: func tlsOverTCPParameters() -> NWParameters { let tcp = NWProtocolTCP.Options() let tls = NWProtocolTLS.Options() let sec = tls.securityProtocolOptions … configure `sec` here … return NWParameters(tls: tls, tcp: tcp) } To enable TLS over UDP, also known as DTLS: func dtlsOverUDPParameters() -> NWParameters { let udp = NWProtocolUDP.Options() let dtls = NWProtocolTLS.Options() let sec = dtls.securityProtocolOptions … configure `sec` here … return NWParameters(dtls: dtls, udp: udp) } To configure TLS with a local digital identity and custom server trust evaluation: func configureTLSPKI(sec: sec_protocol_options_t, identity: SecIdentity) { let secIdentity = sec_identity_create(identity)! sec_protocol_options_set_local_identity(sec, secIdentity) if disableServerTrustEvaluation { sec_protocol_options_set_verify_block(sec, { metadata, secTrust, completionHandler in let trust = sec_trust_copy_ref(secTrust).takeRetainedValue() … evaluate `trust` here … completionHandler(true) }, .main) } } To configure TLS with a pre-shared key: func configureTLSPSK(sec: sec_protocol_options_t, identity: Data, key: Data) { let identityDD = identity.withUnsafeBytes { DispatchData(bytes: $0) } let keyDD = identity.withUnsafeBytes { DispatchData(bytes: $0) } sec_protocol_options_add_pre_shared_key( sec, keyDD as dispatch_data_t, identityDD as dispatch_data_t ) sec_protocol_options_append_tls_ciphersuite( sec, tls_ciphersuite_t(rawValue: TLS_PSK_WITH_AES_128_GCM_SHA256)! ) } Select a network architecture Multipeer Connectivity uses a star network architecture. All peers are equal, and every peer is effectively connected to every peer. Many apps work better with the client/server model, where one peer acts on the server and all the others are clients. Network framework supports both models. To implement a client/server network architecture with Network framework: Designate one peer as the server and all the others as clients. On the server, use NWListener to listen for incoming connections. On each client, use NWConnection to made an outgoing connection to the server. To implement a star network architecture with Network framework: On each peer, start a listener. And also start a connection to each of the other peers. This is likely to generate a lot of redundant connections, as peer A connects to peer B and vice versa. You’ll need to a way to deduplicate those connections, which is the subject of the next section. IMPORTANT While the star network architecture is more likely to create redundant connections, the client/server network architecture can generate redundant connections as well. The advice in the next section applies to both architectures. Create a peer identifier Multipeer Connectivity uses MCPeerID to uniquely identify each peer. There’s nothing particularly magic about MCPeerID; it’s effectively a wrapper around a large random number. To identify each peer in Network framework, generate your own large random number. One good choice for a peer identifier is a locally generated UUID, created using the system UUID type. Some Multipeer Connectivity apps persist their local MCPeerID value, taking advantage of its NSSecureCoding support. You can do the same with a UUID, using either its string representation or its Codable support. IMPORTANT Before you decide to persist a peer identifier, think about the privacy implications. See Design for privacy below. Avoid having multiple connections between peers; that’s both wasteful and potentially confusing. Use your peer identifier to deduplicate connections. Deduplicating connections in a client/server network architecture is easy. Have each client check in with the server with its peer identifier. If the server already has a connection for that identifier, it can either close the old connection and keep the new connection, or vice versa. Deduplicating connections in a star network architecture is a bit trickier. One option is to have each peer send its peer identifier to the other peer and then the peer with the ‘best’ identifier wins. For example, imagine that peer A makes an outgoing connection to peer B while peer B is simultaneously making an outgoing connection to peer A. When a peer receives a peer identifier from a connection, it checks for a duplicate. If it finds one, it compares the peer identifiers and then chooses a connection to drop based on that comparison: if local peer identifier > remote peer identifier then drop outgoing connection else drop incoming connection end if So, peer A drops its incoming connection and peer B drops its outgoing connection. Et voilà! Choose a protocol to match your send mode Multipeer Connectivity offers two send modes, expressed as choices in the MCSessionSendDataMode enum: .reliable for reliable messages .unreliable for best effort messages Best effort is useful when sending latency-sensitive data, that is, data where retransmission is pointless because, by the retransmission arrives, the data will no longer be relevant. This is common in audio and video applications. In Network framework, the send mode is set by the connection’s protocol: A specific QUIC connection is either reliable or best effort. WebSocket and TCP are reliable. UDP is best effort. Start with a reliable connection. In many cases you can stop there, because you never need a best effort connection. If you’re not sure which reliable protocol to use, choose WebSocket. It has key advantages over other protocols: It supports both security models: none and required. Moreover, its required security model supports both TLS-PKI and TLS PSK. In contrast, QUIC only supports the required security model, and within that model it only supports TLS-PKI. It allows you to send messages over the connection. In contrast, TCP works in terms of bytes, meaning that you have to add your own framing. If you need a best effort connection, get started with a reliable connection and use that connection to set up a parallel best effort connection. For example, you might have an exchange like this: Peer A uses its reliable WebSocket connection to peer B to send a request for a parallel best effort UDP connection. Peer B receives that, opens a UDP listener, and sends the UDP listener’s port number back to peer A. Peer A opens its parallel UDP connection to that port on peer B. Note For step 3, get peer B’s IP address from the currentPath property of the reliable WebSocket connection. If you’re not sure which best effort protocol to use, use UDP. While it is possible to use QUIC in datagram mode, it has the same security complexities as QUIC in reliable mode. Discover peers Multipeer Connectivity has a types for advertising a peer’s session (MCAdvertiserAssistant) and a type for browsering for peer (MCNearbyServiceBrowser). In Network framework, configure the listener to advertise its service by setting the service property of NWListener: let listener: NWListener = … listener.service = .init(type: "_example._tcp") listener.serviceRegistrationUpdateHandler = { change in switch change { case .add(let endpoint): … update UI for the added listener endpoint … break case .remove(let endpoint): … update UI for the removed listener endpoint … break @unknown default: break } } listener.stateUpdateHandler = … handle state changes … listener.newConnectionHandler = … handle the new connection … listener.start(queue: .main) This example also shows how to use the serviceRegistrationUpdateHandler to update your UI to reflect changes in the listener. Note This example uses a service type of _example._tcp. See About service types, below, for more details on that. To browse for services, use NWBrowser: let browser = NWBrowser(for: .bonjour(type: "_example._tcp", domain: nil), using: .tcp) browser.browseResultsChangedHandler = { latestResults, _ in … update UI to show the latest results … } browser.stateUpdateHandler = … handle state changes … browser.start(queue: .main) This yields NWEndpoint values for each peer that it discovers. To connect to a given peer, create an NWConnection with that endpoint. About service types The examples in this post use _example._tcp for the service type. The first part, _example, is directly analogous to the serviceType value you supply when creating MCAdvertiserAssistant and MCNearbyServiceBrowser objects. The second part is either _tcp or _udp depending on the underlying transport protocol. For TCP and WebSocket, use _tcp. For UDP and QUIC, use _udp. Service types are described in RFC 6335. If you deploy an app that uses a new service type, register that service type with IANA. Discovery UI Multipeer Connectivity also has UI components for advertising (MCNearbyServiceAdvertiser) and browsing (MCBrowserViewController). There’s no direct equivalent to this in Network framework. Instead, use your preferred UI framework to create a UI that best suits your requirements. Note If you’re targeting Apple TV, check out the DeviceDiscoveryUI framework. Discovery TXT records The Bonjour service discovery protocol used by Network framework supports TXT records. Using these, a listener can associate metadata with its service and a browser can get that metadata for each discovered service. To advertise a TXT record with your listener, include it it the service property value: let listener: NWListener = … let peerID: UUID = … var txtRecord = NWTXTRecord() txtRecord["peerID"] = peerID.uuidString listener.service = .init(type: "_example._tcp", txtRecord: txtRecord.data) To browse for services and their associated TXT records, use the .bonjourWithTXTRecord(…) descriptor: let browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_example._tcp", domain: nil), using: .tcp) browser.browseResultsChangedHandler = { latestResults, _ in for result in latestResults { guard case .bonjour(let txtRecord) = result.metadata, let peerID = txtRecord["peerID"] else { continue } // … examine `result` and `peerID` … _ = peerID } } This example includes the peer identifier in the TXT record with the goal of reducing the number of duplicate connections, but that’s just one potential use for TXT records. Design for privacy This section lists some privacy topics to consider as you implement your app. Obviously this isn’t an exhaustive list. For general advice on this topic, see Protecting the User’s Privacy. There can be no privacy without security. If you didn’t opt in to security with Multipeer Connectivity because you didn’t want to deal with PKI, consider the TLS-PSK options offered by Network framework. For more on this topic, see Plan for security. When you advertise a service, the default behaviour is to use the user-assigned device name as the service name. To override that, create a service with a custom name: let listener: NWListener = … let name: String = … listener.service = .init(name: name, type: "_example._tcp") It’s not uncommon for folks to use the peer identifier as the service name. Whether that’s a good option depends on the user experience of your product: Some products present a list of remote peers and have the user choose from that list. In that case it’s best to stick with the user-assigned device name, because that’s what the user will recognise. Some products automatically connect to services as they discover them. In that case it’s fine to use the peer identifier as the service name, because the user won’t see it anyway. If you stick with the user-assigned device name, consider advertising the peer identifier in your TXT record. See Discovery TXT records. IMPORTANT Using a peer identifier in your service name or TXT record is a heuristic to reduce the number of duplicate connections. Don’t rely on it for correctness. Rather, deduplicate connections using the process described in Create a peer identifier. There are good reasons to persist your peer identifier, but doing so isn’t great for privacy. Persisting the identifier allows for tracking of your service over time and between networks. Consider whether you need a persistent peer identifier at all. If you do, consider whether it makes sense to rotate it over time. A persistent peer identifier is especially worrying if you use it as your service name or put it in your TXT record. Configure your connections Multipeer Connectivity’s symmetric architecture means that it uses a single type, MCSession, to manage the connections to all peers. In Network framework, that role is fulfilled by two types: NWListener to listen for incoming connections. NWConnection to make outgoing connections. Both types require you to supply an NWParameters value that specifies the network protocol and options to use. In addition, when creating an NWConnection you pass in an NWEndpoint to tell it the service to connect to. For example, here’s how to configure a very simple listener for TCP: let parameters = NWParameters.tcp let listener = try NWListener(using: parameters) … continue setting up the listener … And here’s how you might configure an outgoing TCP connection: let parameters = NWParameters.tcp let endpoint = NWEndpoint.hostPort(host: "example.com", port: 80) let connection = NWConnection.init(to: endpoint, using: parameters) … continue setting up the connection … NWParameters has properties to control exactly what protocol to use and what options to use with those protocols. To work with QUIC connections, use code like that shown in the quicParameters() example from the Security section earlier in this post. To work with TCP connections, use the NWParameters.tcp property as shown above. To enable TLS on your TCP connections, use code like that shown in the tlsOverTCPParameters() example from the Security section earlier in this post. To work with WebSocket connections, insert it into the application protocols array: let parameters = NWParameters.tcp let ws = NWProtocolWebSocket.Options(.version13) parameters.defaultProtocolStack.applicationProtocols.insert(ws, at: 0) To enable TLS on your WebSocket connections, use code like that shown in the tlsOverTCPParameters() example to create your base parameters and then add the WebSocket application protocol to that. To work with UDP connections, use the NWParameters.udp property: let parameters = NWParameters.udp To enable TLS on your UDP connections, use code like that shown in the dtlsOverUDPParameters() example from the Security section earlier in this post. Enable peer-to-peer Wi-Fi By default, Network framework doesn’t use peer-to-peer Wi-Fi. To enable that, set the includePeerToPeer property on the parameters used to create your listener and connection objects. parameters.includePeerToPeer = true IMPORTANT Enabling peer-to-peer Wi-Fi can impact the performance of the network. Only opt into it if it’s a significant benefit to your app. If you enable peer-to-peer Wi-Fi, it’s critical to stop network operations as soon as you’re done with them. For example, if you’re browsing for services with peer-to-peer Wi-Fi enabled and the user picks a service, stop the browse operation immediately. Otherwise, the ongoing browse operation might affect the performance of your connection. Manage a listener In Network framework, use NWListener to listen for incoming connections: let parameters: NWParameters = .tcp … configure parameters … let listener = try NWListener(using: parameters) listener.service = … service details … listener.serviceRegistrationUpdateHandler = … handle service registration changes … listener.stateUpdateHandler = { newState in … handle state changes … } listener.newConnectionHandler = { newConnection in … handle the new connection … } listener.start(queue: .main) For details on how to set up parameters, see Configure your connections. For details on how to set up up service and serviceRegistrationUpdateHandler, see Discover peers. Network framework calls your state update handler when the listener changes state: let listener: NWListener = … listener.stateUpdateHandler = { newState in switch newState { case .setup: // The listener has not yet started. … case .waiting(let error): // The listener tried to start and failed. It might recover in the // future. … case .ready: // The listener is running. … case .failed(let error): // The listener tried to start and failed irrecoverably. … case .cancelled: // The listener was cancelled by you. … @unknown default: break } } Network framework calls your new connection handler when a client connects to it: var connections: [NWConnection] = [] let listener: NWListener = listener listener.newConnectionHandler = { newConnection in … configure the new connection … newConnection.start(queue: .main) connections.append(newConnection) } IMPORTANT Don’t forget to call start(queue:) on your connections. In Multipeer Connectivity, the session (MCSession) keeps track of all the peers you’re communicating with. With Network framework, that responsibility falls on you. This example uses a simple connections array for that purpose. In your app you may or may not need a more complex data structure. For example: In the client/server network architecture, the client only needs to manage the connections to a single peer, the server. On the other hand, the server must managed the connections to all client peers. In the star network architecture, every peer must maintain a listener and connections to each of the other peers. Understand UDP flows Network framework handles UDP using the same NWListener and NWConnection types as it uses for TCP. However, the underlying UDP protocol is not implemented in terms of listeners and connections. To resolve this, Network framework works in terms of UDP flows. A UDP flow is defined as a bidirectional sequence of UDP datagrams with the same 4 tuple (local IP address, local port, remote IP address, and remote port). In Network framework: Each NWConnection object manages a single UDP flow. If an NWListener receives a UDP datagram whose 4 tuple doesn’t match any known NWConnection, it creates a new NWConnection. Manage a connection In Network framework, use NWConnection to start an outgoing connection: var connections: [NWConnection] = [] let parameters: NWParameters = … let endpoint: NWEndpoint = … let connection = NWConnection(to: endpoint, using: parameters) connection.stateUpdateHandler = … handle state changes … connection.viabilityUpdateHandler = … handle viability changes … connection.pathUpdateHandler = … handle path changes … connection.betterPathUpdateHandler = … handle better path notifications … connection.start(queue: .main) connections.append(connection) As in the listener case, you’re responsible for keeping track of this connection. Each connection supports four different handlers. Of these, the state and viability update handlers are the most important. For information about the path update and better path handlers, see the NWConnection documentation. Network framework calls your state update handler when the connection changes state: let connection: NWConnection = … connection.stateUpdateHandler = { newState in switch newState { case .setup: // The connection has not yet started. … case .preparing: // The connection is starting. … case .waiting(let error): // The connection tried to start and failed. It might recover in the // future. … case .ready: // The connection is running. … case .failed(let error): // The connection tried to start and failed irrecoverably. … case .cancelled: // The connection was cancelled by you. … @unknown default: break } } If you a connection is in the .waiting(_:) state and you want to force an immediate retry, call the restart() method. Network framework calls your viability update handler when its viability changes: let connection: NWConnection = … connection.viabilityUpdateHandler = { isViable in … react to viability changes … } A connection becomes inviable when a network resource that it depends on is unavailable. A good example of this is the network interface that the connection is running over. If you have a connection running over Wi-Fi, and the user turns off Wi-Fi or moves out of range of their Wi-Fi network, any connection running over Wi-Fi becomes inviable. The inviable state is not necessarily permanent. To continue the above example, the user might re-enable Wi-Fi or move back into range of their Wi-Fi network. If the connection becomes viable again, Network framework calls your viability update handler with a true value. It’s a good idea to debounce the viability handler. If the connection becomes inviable, don’t close it down immediately. Rather, wait for a short while to see if it becomes viable again. If a connection has been inviable for a while, you get to choose as to how to respond. For example, you might close the connection down or inform the user. To close a connection, call the cancel() method. This gracefully disconnects the underlying network connection. To close a connection immediately, call the forceCancel() method. This is not something you should do as a matter of course, but it does make sense in exceptional circumstances. For example, if you’ve determined that the remote peer has gone deaf, it makes sense to cancel it in this way. Send and receive reliable messages In Multipeer Connectivity, a single session supports both reliable and best effort send modes. In Network framework, a connection is either reliable or best effort, depending on the underlying network protocol. The exact mechanism for sending a message depends on the underlying network protocol. A good protocol for reliable messages is WebSocket. To send a message on a WebSocket connection: let connection: NWConnection = … let message: Data = … let metadata = NWProtocolWebSocket.Metadata(opcode: .binary) let context = NWConnection.ContentContext(identifier: "send", metadata: [metadata]) connection.send(content: message, contentContext: context, completion: .contentProcessed({ error in // … check `error` … _ = error })) In WebSocket, the content identifier is ignored. Using an arbitrary fixed value, like the send in this example, is just fine. Multipeer Connectivity allows you to send a message to multiple peers in a single send call. In Network framework each send call targets a specific connection. To send a message to multiple peers, make a send call on the connection associated with each peer. If your app needs to transfer arbitrary amounts of data on a connection, it must implement flow control. See Start a stream, below. To receive messages on a WebSocket connection: func startWebSocketReceive(on connection: NWConnection) { connection.receiveMessage { message, _, _, error in if let error { … handle the error … return } if let message { … handle the incoming message … } startWebSocketReceive(on: connection) } } IMPORTANT WebSocket preserves message boundaries, which is one of the reasons why it’s ideal for your reliable messaging connections. If you use a streaming protocol, like TCP or QUIC streams, you must do your own framing. A good way to do that is with NWProtocolFramer. If you need the metadata associated with the message, get it from the context parameter: connection.receiveMessage { message, context, _, error in … if let message, let metadata = context?.protocolMetadata(definition: NWProtocolWebSocket.definition) as? NWProtocolWebSocket.Metadata { … handle the incoming message and its metadata … } … } Send and receive best effort messages In Multipeer Connectivity, a single session supports both reliable and best effort send modes. In Network framework, a connection is either reliable or best effort, depending on the underlying network protocol. The exact mechanism for sending a message depends on the underlying network protocol. A good protocol for best effort messages is UDP. To send a message on a UDP connection: let connection: NWConnection = … let message: Data = … connection.send(content: message, completion: .idempotent) IMPORTANT UDP datagrams have a theoretical maximum size of just under 64 KiB. However, sending a large datagram results in IP fragmentation, which is very inefficient. For this reason, Network framework prevents you from sending UDP datagrams that will be fragmented. To find the maximum supported datagram size for a connection, gets its maximumDatagramSize property. To receive messages on a UDP connection: func startUDPReceive(on connection: NWConnection) { connection.receiveMessage { message, _, _, error in if let error { … handle the error … return } if let message { … handle the incoming message … } startUDPReceive(on: connection) } } This is exactly the same code as you’d use for WebSocket. Start a stream In Multipeer Connectivity, you can ask the session to start a stream to a specific peer. There are two ways to achieve this in Network framework: If you’re using QUIC for your reliable connection, start a new QUIC stream over that connection. This is one place that QUIC shines. You can run an arbitrary number of QUIC connections over a single QUIC connection group, and QUIC manages flow control (see below) for each connection and for the group as a whole. If you’re using some other protocol for your reliable connection, like WebSocket, you must start a new connection. You might use TCP for this new connection, but it’s not unreasonable to use WebSocket or QUIC. If you need to open a new connection for your stream, you can manage that process over your reliable connection. Choose a protocol to match your send mode explains the general approach for this, although in that case it’s opening a parallel best effort UDP connection rather than a parallel stream connection. The main reason to start a new stream is that you want to send a lot of data to the remote peer. In that case you need to worry about flow control. Flow control applies to both the send and receive side. IMPORTANT Failing to implement flow control can result in unbounded memory growth in your app. This is particularly bad on iOS, where jetsam will terminate your app if it uses too much memory. On the send side, implement flow control by waiting for the connection to call your completion handler before generating and sending more data. For example, on a TCP connection or QUIC stream you might have code like this: func sendNextChunk(on connection: NWConnection) { let chunk: Data = … read next chunk from disk … connection.send(content: chunk, completion: .contentProcessed({ error in if let error { … handle error … return } sendNextChunk(on: connection) })) } This acts like an asynchronous loop. The first send call completes immediately because the connection just copies the data to its send buffer. In response, your app generates more data. This continues until the connection’s send buffer fills up, at which point it defers calling your completion handler. Eventually, the connection moves enough data across the network to free up space in its send buffer, and calls your completion handler. Your app generates another chunk of data For best performance, use a chunk size of at least 64 KiB. If you’re expecting to run on a fast device with a fast network, a chunk size of 1 MiB is reasonable. Receive-side flow control is a natural extension of the standard receive pattern. For example, on a TCP connection or QUIC stream you might have code like this: func receiveNextChunk(on connection: NWConnection) { let chunkSize = 64 * 1024 connection.receive(minimumIncompleteLength: chunkSize, maximumLength: chunkSize) { chunk, _, isComplete, error in if let chunk { … write chunk to disk … } if isComplete { … close the file … return } if let error { … handle the error … return } receiveNextChunk(on: connection) } } IMPORTANT The above is cast in terms of writing the chunk to disk. That’s important, because it prevents unbounded memory growth. If, for example, you accumulated the chunks into an in-memory buffer, that buffer could grow without bound, which risks jetsam terminating your app. The above assumes that you can read and write chunks of data synchronously and promptly, for example, reading and writing a file on a local disk. That’s not always the case. For example, you might be writing data to an accessory over a slow interface, like Bluetooth LE. In such cases you need to read and write each chunk asynchronously. This results in a structure where you read from an asynchronous input and write to an asynchronous output. For an example of how you might approach this, albeit in a very different context, see Handling Flow Copying. Send a resource In Multipeer Connectivity, you can ask the session to send a complete resource, identified by either a file or HTTP URL, to a specific peer. Network framework has no equivalent support for this, but you can implement it on top of a stream: To send, open a stream and then read chunks of data using URLSession and send them over that stream. To receive, open a stream and then receive chunks of data from that stream and write those chunks to disk. In this situation it’s critical to implement flow control, as described in the previous section. Final notes This section collects together some general hints and tips. Concurrency In Multipeer Connectivity, each MCSession has its own internal queue and calls delegate callbacks on that queue. In Network framework, you get to control the queue used by each object for its callbacks. A good pattern is to have a single serial queue for all networking, including your listener and all connections. In a simple app it’s reasonable to use the main queue for networking. If you do this, be careful not to do CPU intensive work in your networking callbacks. For example, if you receive a message that holds JPEG data, don’t decode that data on the main queue. Overriding protocol defaults Many network protocols, most notably TCP and QUIC, are intended to be deployed at vast scale across the wider Internet. For that reason they use default options that aren’t optimised for local networking. Consider changing these defaults in your app. TCP has the concept of a send timeout. If you send data on a TCP connection and TCP is unable to successfully transfer it to the remote peer within the send timeout, TCP will fail the connection. The default send timeout is infinite. TCP just keeps trying. To change this, set the connectionDropTime property. TCP has the concept of keepalives. If a connection is idle, TCP will send traffic on the connection for two reasons: If the connection is running through a NAT, the keepalives prevent the NAT mapping from timing out. If the remote peer is inaccessible, the keepalives fail, which in turn causes the connection to fail. This prevents idle but dead connections from lingering indefinitely. TCP keepalives default to disabled. To enable and configure them, set the enableKeepalive property. To configure their behaviour, set the keepaliveIdle, keepaliveCount, and keepaliveInterval properties. Symbol cross reference If you’re not sure where to start with a specific Multipeer Connectivity construct, find it in the tables below and follow the link to the relevant section. [Sorry for the poor formatting here. DevForums doesn’t support tables properly, so I’ve included the tables as preformatted text.] | For symbol | See | | ----------------------------------- | --------------------------- | | `MCAdvertiserAssistant` | *Discover peers* | | `MCAdvertiserAssistantDelegate` | *Discover peers* | | `MCBrowserViewController` | *Discover peers* | | `MCBrowserViewControllerDelegate` | *Discover peers* | | `MCNearbyServiceAdvertiser` | *Discover peers* | | `MCNearbyServiceAdvertiserDelegate` | *Discover peers* | | `MCNearbyServiceBrowser` | *Discover peers* | | `MCNearbyServiceBrowserDelegate` | *Discover peers* | | `MCPeerID` | *Create a peer identifier* | | `MCSession` | See below. | | `MCSessionDelegate` | See below. | Within MCSession: | For symbol | See | | --------------------------------------------------------- | ------------------------------------ | | `cancelConnectPeer(_:)` | *Manage a connection* | | `connectedPeers` | *Manage a listener* | | `connectPeer(_:withNearbyConnectionData:)` | *Manage a connection* | | `disconnect()` | *Manage a connection* | | `encryptionPreference` | *Plan for security* | | `myPeerID` | *Create a peer identifier* | | `nearbyConnectionData(forPeer:withCompletionHandler:)` | *Discover peers* | | `securityIdentity` | *Plan for security* | | `send(_:toPeers:with:)` | *Send and receive reliable messages* | | `sendResource(at:withName:toPeer:withCompletionHandler:)` | *Send a resource* | | `startStream(withName:toPeer:)` | *Start a stream* | Within MCSessionDelegate: | For symbol | See | | ---------------------------------------------------------------------- | ------------------------------------ | | `session(_:didFinishReceivingResourceWithName:fromPeer:at:withError:)` | *Send a resource* | | `session(_:didReceive:fromPeer:)` | *Send and receive reliable messages* | | `session(_:didReceive:withName:fromPeer:)` | *Start a stream* | | `session(_:didReceiveCertificate:fromPeer:certificateHandler:)` | *Plan for security* | | `session(_:didStartReceivingResourceWithName:fromPeer:with:)` | *Send a resource* | | `session(_:peer:didChange:)` | *Manage a connection* | Revision History 2025-04-11 Added some advice as to whether to use the peer identifier in your service name. Expanded the discussion of how to deduplicate connections in a star network architecture. 2025-03-20 Added a link to the DeviceDiscoveryUI framework to the Discovery UI section. Made other minor editorial changes. 2025-03-11 Expanded the Enable peer-to-peer Wi-Fi section to stress the importance of stopping network operations once you’re done with them. Added a link to that section from the list of Multipeer Connectivity drawbacks. 2025-03-07 First posted.
0
0
1.8k
Apr ’25
"Bug description: We were unable to tap on 'Upgrade to Pro' button."
Hey guys, I am developing a habit tracker app right now. I know it's basic but it's my first app and I wanted an easy start but this is more of a struggle than I thought. So I am trying to publish my app fully on the App Store however everytime I do there is some kind of bug with it. But when I test it, it works perfect for me. Anyways, in this case the reviewer said when they go to the pro page of my app to purchase a pro subscription and try to press upgrade to pro nothing pops up. It pops up normally for me on my iPhone but not other devices. I wanted to make sure if this was normal or if there is something I am supposed to fix. Any help is appreciated. Thank you.
1
0
84
Mar ’25
Socket Becomes Unresponsive in Local Connectivity Extension After Lock Screen
I’m developing an app designed for hospital environments, where public internet access may not be available. The app includes two components: the main app and a Local Connectivity Extension. Both rely on persistent TCP socket connections to communicate with a local server. We’re observing a recurring issue where the extension’s socket becomes unresponsive every 1–3 hours, but only when the device is on the lock screen, even if the main app remains in the foreground. When the screen is not locked, the connection is stable and no disconnections occur. ❗ Issue Details: • What’s going on: The extension sends a keep-alive ping packet every second, and the server replies with a pong and a system time packet. • The bug: The server stops receiving keep alive packets from the extension.  • On the server, we detect about 30 second gap on the server, a gap that shows no packets were received by the extension. This was confirmed via server logs and Wireshark).  • On the extension, from our logs there was no gap in sending packets. From it’s perspective, all packets were sent with no error.  • Because no packet are being received by the server, no packets will be sent to the extension. Eventually the server closes the connection due to keep-alive timeout.  • FYI we log when the NEAppPushProvider subclass sleeps and it did NOT go to sleep while we were debugging. 🧾 Example Logs: Extension log: 2025-03-24 18:34:48.808 sendKeepAliveRequest() 2025-03-24 18:34:49.717 sendKeepAliveRequest() 2025-03-24 18:34:50.692 sendKeepAliveRequest() ... // continuous sending of the ping packet to the server, no problems here 2025-03-24 18:35:55.063 sendKeepAliveRequest() 2025-03-24 18:35:55.063 keepAliveTimer IS TIME OUT... in CoreService. // this is triggered because we did not receive any packets from the server 2025-03-24 18:34:16.298 No keep-alive received for 16 seconds... connection ID=95b3... // this shows that there has been no packets being received by the extension ... 2025-03-24 18:34:30.298 Connection timed out on keep-alive. connection ID=95b3... // eventually closes due to no packets being received 2025-03-24 18:34:30.298 Remote Subsystem Disconnected {name=iPhone|Replica-Ext|...} ✅ Observations: • The extension process continues running and logging keep-alive attempts. • However, network traffic stops reaching the server, and no inbound packets are received by the extension. • It looks like the socket becomes silently suspended or frozen, without being properly closed or throwing an error. ❓Questions: • Do you know why this might happen within a Local Connectivity Extension, especially under foreground conditions and locked ? • Is there any known system behavior that might cause the socket to be suspended or blocked in this way after running for a few hours? Any insights or recommendations would be greatly appreciated. Thank you!
1
0
87
Mar ’25
Waiting for HID Entitlements for MONTHS
Hi Apple support, We requested the 4 HID-related Entitlements back in December 2024. Similarly to another post here in the forums that was completely ignored, our request has NOT been processed for months. Mailing the support staff results in boilerplate email responses with no content, calling them results in a chat with very nice people who are unable to help since they can't seem to reach the entitlement team directly. Having to wait for MONTHS when dealing with one of the biggest and supposedly best companies in the world is beyond disappointing. Can anyone help? Is there anyone else that has had this same issue and that has found a work-around? I can share all necessary details. Thanks, Matteo
1
0
168
Apr ’25
Dext not initializing with a log "Failed to write extension load report plist"
When plugging in my matched USB device I see the logs below. It seems the kernelmanagerd process is sandboxed and can't write out the reason my Dext failed to load. Is there somewhere else I can look for this info? default 11:03:22.175152-0700 kernelmanagerd Received kext load notification: me.keithg.MyUserUSBInterfaceDriver default 11:03:22.177637-0700 kernel 1 duplicate report for Sandbox: icdd(2124) allow file-read-data /Library/Image Capture/Devices error 11:03:22.177681-0700 kernel Sandbox: kernelmanagerd(545) deny(1) file-write-create /private/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/com.apple.kernelmanagerd/TemporaryItems com.apple.libcoreservices error 11:03:22.177711-0700 kernelmanagerd mkdir: path=/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/com.apple.kernelmanagerd/TemporaryItems/ mode= -rwx------: [1: Operation not permitted] error 11:03:22.179361-0700 kernel Sandbox: kernelmanagerd(545) deny(1) file-write-create /private/var/db/loadedkextmt.plist.sb-5a00fc77-LNttZF com.apple.libcoreservices error 11:03:22.177755-0700 kernelmanagerd _dirhelper_relative_internal: error for path <private>: [1: Operation not permitted] com.apple.accessories default 11:03:22.177674-0700 WindowServer Sending analytics event... (eventName: com.apple.ioport.transport.USB.published) error 11:03:22.179913-0700 kernelmanagerd Failed to write extension load report plist.
1
0
265
Mar ’25
StoreKit Subscription Fails to Load During App Review, Works in Sandbox/TestFlight
Hi everyone, I’m facing a recurring issue with my macOS app being rejected during App Store review, and I’d really appreciate any guidance. The subscription flow in my app is implemented using StoreKit, and everything works perfectly in our development environment using a StoreKit configuration file. It also behaves as expected in Sandbox testing and TestFlight — I even had few beta testers confirm that the subscription information is displayed correctly and the purchase flow completes without issues. All required subscription details are configured in App Store Connect: • Subscription duration and the description of the services offered • Price and price per unit where applicable • Paid apps agreement and related forms are correctly filled However, when the app is submitted for review, the subscription screen fails to display the expected information. From what I can tell, the product information fails to load from the App Store in the review environment — even though everything is working fine on our side. We’ve already submitted a video to Apple showing the subscription UI working in the Sandbox environment, but the app continues to be rejected under guideline 3.1.2 due to missing subscription info in the binary. Is anyone else experiencing similar behavior during review? Could there be a caching issue or delay in StoreKit syncing for newly configured products? Any help or suggestions are very welcome. Thanks in advance!
Replies
0
Boosts
0
Views
111
Activity
Apr ’25
Apple Pay - PKAddPaymentPassViewController doesn't show correct list of devices
Hi, We are trying to make the PKAddPaymentPassViewController to show the correct list of devices to where the pass can be added. We have analysed the documentation and we are using the PrimaryAccountIdentifier field which is the field that supposedly controls this behavior but the list of devices presented in the view controller always include one iPhone and one Apple Watch, regardless of where the card has been already added. We are initializing the PKAddPaymentPassRequestConfiguration object with: PKEncryptionScheme PrimaryAccountIdentifier CardholderName PrimaryAccountSuffix LocalizedDescription PaymentNetwork PrimaryAccountIdentifier CardholderName PrimaryAccountSuffix LocalizedDescription We have also verified the configuration in our payment pass processor and everything should be ok. We would like to have some help on achieving the desired flow for Apple Pay, which is to present the PKAddPaymentPassViewController with the correct list of available devices and not the full list. Thank you.
Replies
1
Boosts
0
Views
576
Activity
Apr ’25
What does iOS do wrt Shared Web Credentials when it makes a call to a server to perform a message filter request
In order to create a Message Filter Extension it is necessary to set up Shared Web Credentials. I'd like to form an understanding of what role SWC plays when the OS is making request to the associated network service (when the extension has called deferQueryRequestToNetwork()) and how this differs from when an app directly uses Shared Web Credentials itself. When an app is making direct use of SWC, it makes a request to obtain the user's credentials from the web site. However in the case of a Message Filter Extension, there aren't any individual user credentials, so what is happening behind the scenes when the OS makes a server request on behalf of a Message Filtering Extension? A more general question - the documentation for Shared Web Credentials says "Associated domains establish a secure association between domains and your app.". Thank you
Replies
2
Boosts
0
Views
473
Activity
Apr ’25
Apple Pay integration with JS
I am facing an issue with Apple Pay js while doing the integration we are using reference https://applepaydemo.apple.com/apple-pay-js-api In this I can generate the merchantSession correctly But when I pass that merchantSession in session.completeMerchantValidation(merchantValidation) as per documentation It is getting failed and also no appropriate error is being shown in the console
Replies
0
Boosts
0
Views
139
Activity
Apr ’25
Weatherkit - visibility units and height
reposting this in case it got missed the first time around here https://developer.apple.com/forums/thread/775900 We had a question that came up when we comparing data from WeatherKit to other sources - WeatherKit visibility was well beyond the boundaries we had historically, even from Darksky. That raises two questions: is visibility actually in meters like the docs say? is this visibility at ground level, 500ft, or some other height? We were seeing visibility numbers of up to 40 miles (after converting the number the API sent to miles), where all of our other sources are usually within 10 miles
Replies
0
Boosts
0
Views
70
Activity
Apr ’25
Custom IPSec IKEv2 with Packet Tunnel Provider Extension on iOS
We’re looking to implement a custom IPSec IKEv2 VPN using the Packet Tunnel Provider network extension on iOS because we need to add extra information to EAP, which the built-in IKEv2 VPN configuration does not support. Is it possible to handle the full IKEv2 negotiation and IPSec tunneling within the Packet Tunnel Provider extension? Or are there limitations that would prevent implementing a full IKEv2 stack this way? Any insights or alternative approaches would be appreciated. Thanks!
Replies
1
Boosts
0
Views
108
Activity
Mar ’25
The Apple Pay interface is not responding
My server's access to Apple's payment interface (buy. itunes. apple. com/verifiyReceipt) has been unresponsive since the end of March, and I have been searching for a long time without finding any issues. Normally, even if the data is incorrect, there is still a {"status": 21000} response. We are using Alibaba Cloud's virtual servers here. I don't know if Apple has made any adjustments to the interface. If anyone has encountered this problem, please kindly help to answer it. Thank you all.
Replies
0
Boosts
0
Views
69
Activity
Apr ’25
NotSupportedError, The payment method is not supported
What am I missing in my checking for whether or not to offer Apple Pay on my website? <script async crossorigin src="https://applepay.cdn-apple.com/jsapi/v1.1.0/apple-pay-sdk.js" ></script> ... <style> apple-pay-button { display: none; } </style> ... <apple-pay-button buttonstyle="black" type="plain" locale="en-US" onclick="startApplePay('${APPLE_PAY_MERCHANT_ID}','${paymentForm.amount}');"></apple-pay-button> So, the button is not displayed by default. I only change the style to displayed if: window.onload = function() { if (isApplePaySupported()) { document.querySelector("apple-pay-button").style.display = "inline-block"; }; } function isApplePaySupported() { return (window.PaymentRequest && window.ApplePaySession && ApplePaySession.canMakePayments() && ApplePaySession.supportsVersion(applePayVersion)); } Yet, once in a while a click comes through that tries to create a PaymentRequest with const applePayMethod = { "supportedMethods": "https://apple.com/apple-pay", "data": { "version": applePayVersion, "merchantIdentifier": merchantIdentifier, "merchantCapabilities": [ "supports3DS" ], "supportedNetworks": [ "amex", "discover", "masterCard", "visa" ], "countryCode": "US" } }; and results in: NotSupportedError, The payment method is not supported What else might be "not supported" in the request for this particular user/device/wallet? In particular, that could be known immediately when the PaymentRequest is created, but before any payment instrument from the wallet is selected? And, is there anything I could detect before showing the button? Or, is it even possible for the button to be clicked by some kind of automation, even if it's not displayed?
Replies
1
Boosts
0
Views
100
Activity
Apr ’25
Storekit2 Some device failure unknown
iOS Storekit2 Appstore production environment, some user feedback in app purchase faliure, What our log records is StoreKitError.unknown,please How to solve problem, thanks
Replies
1
Boosts
0
Views
122
Activity
Apr ’25
Live Activity Start Token not generating after certain days of usage for non Production builds
Live Activity Start Token not generating after certain days of usage. We have implemented Live Activity feature where the initial activity is launched by our backend. But to start that first live activity I need push to start token which is generating for few days but all of sudden after certain days it stops generating. Currently we are in development phase so we test it on multiple devices and multiple time we are doing install and uninstall. STEPS TO REPRODUCE Install the app Start token gets generated which is sent to our server After certain duration server sends the first live activity using that token user opens the app then we receive the updated token and send that token to server server uses that updated token to further update the live activity. All this works fine. But after a week of usage we are observing that we stop getting start token from APNS. Not sure where exactly the thing is breaking. We have tried with different devices and different bundle identifiers but behaviour is same for all. func generateStartToken() { Task.detached { [weak self] in guard let self else { return } await self.observeActivityPushTokenAndState() for await data in ActivityKit.Activity<LiveActivityAttribute>.pushToStartTokenUpdates { let token = data.map { String(format: "%02x", $0) }.joined() print("Activity Start token: ", token) } } } func observeActivityPushTokenAndState() { Task.detached { for await activity in ActivityKit.Activity<LiveActivityAttribute>.activityUpdates { Task { for await tokenData in activity.pushTokenUpdates { let updatedToken = tokenData.map { String(format: "%02x", $0) }.joined() print("Activity Update token: ", updatedToken) } } Task { for await content in activity.contentUpdates { let updatedContent = content.state print("Activity Updated: ", updatedContent) } } } } }
Replies
2
Boosts
0
Views
188
Activity
Apr ’25
Using any SwiftData Query causes app to hang
I want to get to a point where I can use a small view with a query for my SwiftData model like this: @Query private var currentTrainingCycle: [TrainingCycle] init(/*currentDate: Date*/) { _currentTrainingCycle = Query(filter: #Predicate<TrainingCycle> { $0.numberOfDays > 0 // $0.startDate < currentDate && currentDate < $0.endDate }, sort: \.startDate) } The commented code is where I want to go. In this instance, it'd be created as a lazy var in a viewModel to have it stable (and not constantly re-creating the view). Since it was not working, I thought I could check the same view with a query that does not require any dynamic input. In this case, the numberOfDays never changes after instantiation. But still, each time the app tries to create this view, the app becomes unresponsive, the CPU usage goes at 196%, memory goes way high and the device heats up quickly. Am I holding it wrong? How can I have a dynamic predicate on a View in SwiftUI with SwiftData?
Replies
2
Boosts
0
Views
238
Activity
Mar ’25
Combining Screen Time Usage with HealthKit Data in a Chart
Hello Apple Developer Community, I’m working on creating a chart that combines Screen Time Usage data with Workout Time from HealthKit. I’ve successfully implemented a DeviceActivityReportExtension to fetch Screen Time data and draw a chart. I’m also able to read HealthKit data from the main app. However, I’m having trouble integrating the HealthKit data into the View generated by the DeviceActivityReportExtension. I’ve attempted to read HealthKit data directly from the extension , but this doesn’t seem to work, likely due to HealthKit access restrictions in extensions. I also tied using a shared object to pass HealthKit data to the extension, but unfortunately this didn’t seem to work as expected. I’d greatly appreciate any suggestions on how to successfully integrate HealthKit data into the extension-generated View. Has anyone dealt with a similar challenge or found a workaround for this? Thanks in advance for your help!
Replies
0
Boosts
0
Views
103
Activity
Mar ’25
Issue Integrating Apple Pay JS – `merchantSession` Blocke
Hello Apple Devs, We’re currently trying to integrate Apple Pay on the web using Apple Pay JS. We've followed the official documentation closely, but we're running into a blocker during the merchantSession validation phase. We successfully retrieved a merchantSession, which looks like this: json { "displayName": "Our Name", "domainName": "https://pay.ourdomain.co", "epochTimestamp": , "expiresAt": ****************, "merchantIdentifier": "", "merchantSessionIdentifier": ", "nonce": "", "operationalAnalyticsIdentifier": our name "t:", "pspId": "", "retries": 0, "signature": "*****************..." } Issue: Shortly after initiating the session, we receive a cancel event with the following info: ApplePayCancelEvent { type: "cancel", sessionError: { code: "unknown", info: {} } } We're unsure what causes the cancellation. There are no clear error messages or hints in the logs to identify what went wrong. What We’ve Checked: The merchantSession is returned successfully from our backend. The domainName matches our frontend domain (https://pay.durdomain.co). The session hasn’t expired when tested. We're using Apple Pay JS APIs as described in the documentation. Help Needed: What can trigger an ApplePayCancelEvent with an "unknown" error code? Any insight or guidance would be deeply appreciated. Thanks in advance!
Replies
1
Boosts
0
Views
132
Activity
Apr ’25
Moving from Multipeer Connectivity to Network Framework
I see a lot of folks spend a lot of time trying to get Multipeer Connectivity to work for them. My experience is that the final result is often unsatisfactory. Instead, my medium-to-long term recommendation is to use Network framework instead. This post explains how you might move from Multipeer Connectivity to Network framework. If you have questions or comments, put them in a new thread. Place it in the App & System Services > Networking topic area and tag it with Multipeer Connectivity and Network framework. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Moving from Multipeer Connectivity to Network Framework Multipeer Connectivity has a number of drawbacks: It has an opinionated networking model, where every participant in a session is a symmetric peer. Many apps work better with the traditional client/server model. It offers good latency but poor throughput. It doesn’t support flow control, aka back pressure, which severely constrains its utility for general-purpose networking. It includes a number of UI components that are effectively obsolete. It hasn’t evolved in recent years. For example, it relies on NSStream, which has been scheduled for deprecation as far as networking is concerned. It always enables peer-to-peer Wi-Fi, something that’s not required for many apps and can impact the performance of the network (see Enable peer-to-peer Wi-Fi, below, for more about this). Its security model requires the use of PKI — public key infrastructure, that is, digital identities and certificates — which are tricky to deploy in a peer-to-peer environment. It has some gnarly bugs. IMPORTANT Many folks use Multipeer Connectivity because they think it’s the only way to use peer-to-peer Wi-Fi. That’s not the case. Network framework has opt-in peer-to-peer Wi-Fi support. See Enable peer-to-peer Wi-Fi, below. If Multipeer Connectivity is not working well for you, consider moving to Network framework. This post explains how to do that in 13 easy steps (-: Plan for security Select a network architecture Create a peer identifier Choose a protocol to match your send mode Discover peers Design for privacy Configure your connections Manage a listener Manage a connection Send and receive reliable messages Send and receive best effort messages Start a stream Send a resource Finally, at the end of the post you’ll find two appendices: Final notes contains some general hints and tips. Symbol cross reference maps symbols in the Multipeer Connectivity framework to sections of this post. Consult it if you’re not sure where to start with a specific Multipeer Connectivity construct. Plan for security The first thing you need to think about is security. Multipeer Connectivity offers three security models, expressed as choices in the MCEncryptionPreference enum: .none for no security .optional for optional security .required for required security For required security each peer must have a digital identity. Optional security is largely pointless. It’s more complex than no security but doesn’t yield any benefits. So, in this post we’ll focus on the no security and required security models. Your security choice affects the network protocols you can use: QUIC is always secure. WebSocket, TCP, and UDP can be used with and without TLS security. QUIC security only supports PKI. TLS security supports both TLS-PKI and pre-shared key (PSK). You might find that TLS-PSK is easier to deploy in a peer-to-peer environment. To configure the security of the QUIC protocol: func quicParameters() -> NWParameters { let quic = NWProtocolQUIC.Options(alpn: ["MyAPLN"]) let sec = quic.securityProtocolOptions … configure `sec` here … return NWParameters(quic: quic) } To enable TLS over TCP: func tlsOverTCPParameters() -> NWParameters { let tcp = NWProtocolTCP.Options() let tls = NWProtocolTLS.Options() let sec = tls.securityProtocolOptions … configure `sec` here … return NWParameters(tls: tls, tcp: tcp) } To enable TLS over UDP, also known as DTLS: func dtlsOverUDPParameters() -> NWParameters { let udp = NWProtocolUDP.Options() let dtls = NWProtocolTLS.Options() let sec = dtls.securityProtocolOptions … configure `sec` here … return NWParameters(dtls: dtls, udp: udp) } To configure TLS with a local digital identity and custom server trust evaluation: func configureTLSPKI(sec: sec_protocol_options_t, identity: SecIdentity) { let secIdentity = sec_identity_create(identity)! sec_protocol_options_set_local_identity(sec, secIdentity) if disableServerTrustEvaluation { sec_protocol_options_set_verify_block(sec, { metadata, secTrust, completionHandler in let trust = sec_trust_copy_ref(secTrust).takeRetainedValue() … evaluate `trust` here … completionHandler(true) }, .main) } } To configure TLS with a pre-shared key: func configureTLSPSK(sec: sec_protocol_options_t, identity: Data, key: Data) { let identityDD = identity.withUnsafeBytes { DispatchData(bytes: $0) } let keyDD = identity.withUnsafeBytes { DispatchData(bytes: $0) } sec_protocol_options_add_pre_shared_key( sec, keyDD as dispatch_data_t, identityDD as dispatch_data_t ) sec_protocol_options_append_tls_ciphersuite( sec, tls_ciphersuite_t(rawValue: TLS_PSK_WITH_AES_128_GCM_SHA256)! ) } Select a network architecture Multipeer Connectivity uses a star network architecture. All peers are equal, and every peer is effectively connected to every peer. Many apps work better with the client/server model, where one peer acts on the server and all the others are clients. Network framework supports both models. To implement a client/server network architecture with Network framework: Designate one peer as the server and all the others as clients. On the server, use NWListener to listen for incoming connections. On each client, use NWConnection to made an outgoing connection to the server. To implement a star network architecture with Network framework: On each peer, start a listener. And also start a connection to each of the other peers. This is likely to generate a lot of redundant connections, as peer A connects to peer B and vice versa. You’ll need to a way to deduplicate those connections, which is the subject of the next section. IMPORTANT While the star network architecture is more likely to create redundant connections, the client/server network architecture can generate redundant connections as well. The advice in the next section applies to both architectures. Create a peer identifier Multipeer Connectivity uses MCPeerID to uniquely identify each peer. There’s nothing particularly magic about MCPeerID; it’s effectively a wrapper around a large random number. To identify each peer in Network framework, generate your own large random number. One good choice for a peer identifier is a locally generated UUID, created using the system UUID type. Some Multipeer Connectivity apps persist their local MCPeerID value, taking advantage of its NSSecureCoding support. You can do the same with a UUID, using either its string representation or its Codable support. IMPORTANT Before you decide to persist a peer identifier, think about the privacy implications. See Design for privacy below. Avoid having multiple connections between peers; that’s both wasteful and potentially confusing. Use your peer identifier to deduplicate connections. Deduplicating connections in a client/server network architecture is easy. Have each client check in with the server with its peer identifier. If the server already has a connection for that identifier, it can either close the old connection and keep the new connection, or vice versa. Deduplicating connections in a star network architecture is a bit trickier. One option is to have each peer send its peer identifier to the other peer and then the peer with the ‘best’ identifier wins. For example, imagine that peer A makes an outgoing connection to peer B while peer B is simultaneously making an outgoing connection to peer A. When a peer receives a peer identifier from a connection, it checks for a duplicate. If it finds one, it compares the peer identifiers and then chooses a connection to drop based on that comparison: if local peer identifier > remote peer identifier then drop outgoing connection else drop incoming connection end if So, peer A drops its incoming connection and peer B drops its outgoing connection. Et voilà! Choose a protocol to match your send mode Multipeer Connectivity offers two send modes, expressed as choices in the MCSessionSendDataMode enum: .reliable for reliable messages .unreliable for best effort messages Best effort is useful when sending latency-sensitive data, that is, data where retransmission is pointless because, by the retransmission arrives, the data will no longer be relevant. This is common in audio and video applications. In Network framework, the send mode is set by the connection’s protocol: A specific QUIC connection is either reliable or best effort. WebSocket and TCP are reliable. UDP is best effort. Start with a reliable connection. In many cases you can stop there, because you never need a best effort connection. If you’re not sure which reliable protocol to use, choose WebSocket. It has key advantages over other protocols: It supports both security models: none and required. Moreover, its required security model supports both TLS-PKI and TLS PSK. In contrast, QUIC only supports the required security model, and within that model it only supports TLS-PKI. It allows you to send messages over the connection. In contrast, TCP works in terms of bytes, meaning that you have to add your own framing. If you need a best effort connection, get started with a reliable connection and use that connection to set up a parallel best effort connection. For example, you might have an exchange like this: Peer A uses its reliable WebSocket connection to peer B to send a request for a parallel best effort UDP connection. Peer B receives that, opens a UDP listener, and sends the UDP listener’s port number back to peer A. Peer A opens its parallel UDP connection to that port on peer B. Note For step 3, get peer B’s IP address from the currentPath property of the reliable WebSocket connection. If you’re not sure which best effort protocol to use, use UDP. While it is possible to use QUIC in datagram mode, it has the same security complexities as QUIC in reliable mode. Discover peers Multipeer Connectivity has a types for advertising a peer’s session (MCAdvertiserAssistant) and a type for browsering for peer (MCNearbyServiceBrowser). In Network framework, configure the listener to advertise its service by setting the service property of NWListener: let listener: NWListener = … listener.service = .init(type: "_example._tcp") listener.serviceRegistrationUpdateHandler = { change in switch change { case .add(let endpoint): … update UI for the added listener endpoint … break case .remove(let endpoint): … update UI for the removed listener endpoint … break @unknown default: break } } listener.stateUpdateHandler = … handle state changes … listener.newConnectionHandler = … handle the new connection … listener.start(queue: .main) This example also shows how to use the serviceRegistrationUpdateHandler to update your UI to reflect changes in the listener. Note This example uses a service type of _example._tcp. See About service types, below, for more details on that. To browse for services, use NWBrowser: let browser = NWBrowser(for: .bonjour(type: "_example._tcp", domain: nil), using: .tcp) browser.browseResultsChangedHandler = { latestResults, _ in … update UI to show the latest results … } browser.stateUpdateHandler = … handle state changes … browser.start(queue: .main) This yields NWEndpoint values for each peer that it discovers. To connect to a given peer, create an NWConnection with that endpoint. About service types The examples in this post use _example._tcp for the service type. The first part, _example, is directly analogous to the serviceType value you supply when creating MCAdvertiserAssistant and MCNearbyServiceBrowser objects. The second part is either _tcp or _udp depending on the underlying transport protocol. For TCP and WebSocket, use _tcp. For UDP and QUIC, use _udp. Service types are described in RFC 6335. If you deploy an app that uses a new service type, register that service type with IANA. Discovery UI Multipeer Connectivity also has UI components for advertising (MCNearbyServiceAdvertiser) and browsing (MCBrowserViewController). There’s no direct equivalent to this in Network framework. Instead, use your preferred UI framework to create a UI that best suits your requirements. Note If you’re targeting Apple TV, check out the DeviceDiscoveryUI framework. Discovery TXT records The Bonjour service discovery protocol used by Network framework supports TXT records. Using these, a listener can associate metadata with its service and a browser can get that metadata for each discovered service. To advertise a TXT record with your listener, include it it the service property value: let listener: NWListener = … let peerID: UUID = … var txtRecord = NWTXTRecord() txtRecord["peerID"] = peerID.uuidString listener.service = .init(type: "_example._tcp", txtRecord: txtRecord.data) To browse for services and their associated TXT records, use the .bonjourWithTXTRecord(…) descriptor: let browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_example._tcp", domain: nil), using: .tcp) browser.browseResultsChangedHandler = { latestResults, _ in for result in latestResults { guard case .bonjour(let txtRecord) = result.metadata, let peerID = txtRecord["peerID"] else { continue } // … examine `result` and `peerID` … _ = peerID } } This example includes the peer identifier in the TXT record with the goal of reducing the number of duplicate connections, but that’s just one potential use for TXT records. Design for privacy This section lists some privacy topics to consider as you implement your app. Obviously this isn’t an exhaustive list. For general advice on this topic, see Protecting the User’s Privacy. There can be no privacy without security. If you didn’t opt in to security with Multipeer Connectivity because you didn’t want to deal with PKI, consider the TLS-PSK options offered by Network framework. For more on this topic, see Plan for security. When you advertise a service, the default behaviour is to use the user-assigned device name as the service name. To override that, create a service with a custom name: let listener: NWListener = … let name: String = … listener.service = .init(name: name, type: "_example._tcp") It’s not uncommon for folks to use the peer identifier as the service name. Whether that’s a good option depends on the user experience of your product: Some products present a list of remote peers and have the user choose from that list. In that case it’s best to stick with the user-assigned device name, because that’s what the user will recognise. Some products automatically connect to services as they discover them. In that case it’s fine to use the peer identifier as the service name, because the user won’t see it anyway. If you stick with the user-assigned device name, consider advertising the peer identifier in your TXT record. See Discovery TXT records. IMPORTANT Using a peer identifier in your service name or TXT record is a heuristic to reduce the number of duplicate connections. Don’t rely on it for correctness. Rather, deduplicate connections using the process described in Create a peer identifier. There are good reasons to persist your peer identifier, but doing so isn’t great for privacy. Persisting the identifier allows for tracking of your service over time and between networks. Consider whether you need a persistent peer identifier at all. If you do, consider whether it makes sense to rotate it over time. A persistent peer identifier is especially worrying if you use it as your service name or put it in your TXT record. Configure your connections Multipeer Connectivity’s symmetric architecture means that it uses a single type, MCSession, to manage the connections to all peers. In Network framework, that role is fulfilled by two types: NWListener to listen for incoming connections. NWConnection to make outgoing connections. Both types require you to supply an NWParameters value that specifies the network protocol and options to use. In addition, when creating an NWConnection you pass in an NWEndpoint to tell it the service to connect to. For example, here’s how to configure a very simple listener for TCP: let parameters = NWParameters.tcp let listener = try NWListener(using: parameters) … continue setting up the listener … And here’s how you might configure an outgoing TCP connection: let parameters = NWParameters.tcp let endpoint = NWEndpoint.hostPort(host: "example.com", port: 80) let connection = NWConnection.init(to: endpoint, using: parameters) … continue setting up the connection … NWParameters has properties to control exactly what protocol to use and what options to use with those protocols. To work with QUIC connections, use code like that shown in the quicParameters() example from the Security section earlier in this post. To work with TCP connections, use the NWParameters.tcp property as shown above. To enable TLS on your TCP connections, use code like that shown in the tlsOverTCPParameters() example from the Security section earlier in this post. To work with WebSocket connections, insert it into the application protocols array: let parameters = NWParameters.tcp let ws = NWProtocolWebSocket.Options(.version13) parameters.defaultProtocolStack.applicationProtocols.insert(ws, at: 0) To enable TLS on your WebSocket connections, use code like that shown in the tlsOverTCPParameters() example to create your base parameters and then add the WebSocket application protocol to that. To work with UDP connections, use the NWParameters.udp property: let parameters = NWParameters.udp To enable TLS on your UDP connections, use code like that shown in the dtlsOverUDPParameters() example from the Security section earlier in this post. Enable peer-to-peer Wi-Fi By default, Network framework doesn’t use peer-to-peer Wi-Fi. To enable that, set the includePeerToPeer property on the parameters used to create your listener and connection objects. parameters.includePeerToPeer = true IMPORTANT Enabling peer-to-peer Wi-Fi can impact the performance of the network. Only opt into it if it’s a significant benefit to your app. If you enable peer-to-peer Wi-Fi, it’s critical to stop network operations as soon as you’re done with them. For example, if you’re browsing for services with peer-to-peer Wi-Fi enabled and the user picks a service, stop the browse operation immediately. Otherwise, the ongoing browse operation might affect the performance of your connection. Manage a listener In Network framework, use NWListener to listen for incoming connections: let parameters: NWParameters = .tcp … configure parameters … let listener = try NWListener(using: parameters) listener.service = … service details … listener.serviceRegistrationUpdateHandler = … handle service registration changes … listener.stateUpdateHandler = { newState in … handle state changes … } listener.newConnectionHandler = { newConnection in … handle the new connection … } listener.start(queue: .main) For details on how to set up parameters, see Configure your connections. For details on how to set up up service and serviceRegistrationUpdateHandler, see Discover peers. Network framework calls your state update handler when the listener changes state: let listener: NWListener = … listener.stateUpdateHandler = { newState in switch newState { case .setup: // The listener has not yet started. … case .waiting(let error): // The listener tried to start and failed. It might recover in the // future. … case .ready: // The listener is running. … case .failed(let error): // The listener tried to start and failed irrecoverably. … case .cancelled: // The listener was cancelled by you. … @unknown default: break } } Network framework calls your new connection handler when a client connects to it: var connections: [NWConnection] = [] let listener: NWListener = listener listener.newConnectionHandler = { newConnection in … configure the new connection … newConnection.start(queue: .main) connections.append(newConnection) } IMPORTANT Don’t forget to call start(queue:) on your connections. In Multipeer Connectivity, the session (MCSession) keeps track of all the peers you’re communicating with. With Network framework, that responsibility falls on you. This example uses a simple connections array for that purpose. In your app you may or may not need a more complex data structure. For example: In the client/server network architecture, the client only needs to manage the connections to a single peer, the server. On the other hand, the server must managed the connections to all client peers. In the star network architecture, every peer must maintain a listener and connections to each of the other peers. Understand UDP flows Network framework handles UDP using the same NWListener and NWConnection types as it uses for TCP. However, the underlying UDP protocol is not implemented in terms of listeners and connections. To resolve this, Network framework works in terms of UDP flows. A UDP flow is defined as a bidirectional sequence of UDP datagrams with the same 4 tuple (local IP address, local port, remote IP address, and remote port). In Network framework: Each NWConnection object manages a single UDP flow. If an NWListener receives a UDP datagram whose 4 tuple doesn’t match any known NWConnection, it creates a new NWConnection. Manage a connection In Network framework, use NWConnection to start an outgoing connection: var connections: [NWConnection] = [] let parameters: NWParameters = … let endpoint: NWEndpoint = … let connection = NWConnection(to: endpoint, using: parameters) connection.stateUpdateHandler = … handle state changes … connection.viabilityUpdateHandler = … handle viability changes … connection.pathUpdateHandler = … handle path changes … connection.betterPathUpdateHandler = … handle better path notifications … connection.start(queue: .main) connections.append(connection) As in the listener case, you’re responsible for keeping track of this connection. Each connection supports four different handlers. Of these, the state and viability update handlers are the most important. For information about the path update and better path handlers, see the NWConnection documentation. Network framework calls your state update handler when the connection changes state: let connection: NWConnection = … connection.stateUpdateHandler = { newState in switch newState { case .setup: // The connection has not yet started. … case .preparing: // The connection is starting. … case .waiting(let error): // The connection tried to start and failed. It might recover in the // future. … case .ready: // The connection is running. … case .failed(let error): // The connection tried to start and failed irrecoverably. … case .cancelled: // The connection was cancelled by you. … @unknown default: break } } If you a connection is in the .waiting(_:) state and you want to force an immediate retry, call the restart() method. Network framework calls your viability update handler when its viability changes: let connection: NWConnection = … connection.viabilityUpdateHandler = { isViable in … react to viability changes … } A connection becomes inviable when a network resource that it depends on is unavailable. A good example of this is the network interface that the connection is running over. If you have a connection running over Wi-Fi, and the user turns off Wi-Fi or moves out of range of their Wi-Fi network, any connection running over Wi-Fi becomes inviable. The inviable state is not necessarily permanent. To continue the above example, the user might re-enable Wi-Fi or move back into range of their Wi-Fi network. If the connection becomes viable again, Network framework calls your viability update handler with a true value. It’s a good idea to debounce the viability handler. If the connection becomes inviable, don’t close it down immediately. Rather, wait for a short while to see if it becomes viable again. If a connection has been inviable for a while, you get to choose as to how to respond. For example, you might close the connection down or inform the user. To close a connection, call the cancel() method. This gracefully disconnects the underlying network connection. To close a connection immediately, call the forceCancel() method. This is not something you should do as a matter of course, but it does make sense in exceptional circumstances. For example, if you’ve determined that the remote peer has gone deaf, it makes sense to cancel it in this way. Send and receive reliable messages In Multipeer Connectivity, a single session supports both reliable and best effort send modes. In Network framework, a connection is either reliable or best effort, depending on the underlying network protocol. The exact mechanism for sending a message depends on the underlying network protocol. A good protocol for reliable messages is WebSocket. To send a message on a WebSocket connection: let connection: NWConnection = … let message: Data = … let metadata = NWProtocolWebSocket.Metadata(opcode: .binary) let context = NWConnection.ContentContext(identifier: "send", metadata: [metadata]) connection.send(content: message, contentContext: context, completion: .contentProcessed({ error in // … check `error` … _ = error })) In WebSocket, the content identifier is ignored. Using an arbitrary fixed value, like the send in this example, is just fine. Multipeer Connectivity allows you to send a message to multiple peers in a single send call. In Network framework each send call targets a specific connection. To send a message to multiple peers, make a send call on the connection associated with each peer. If your app needs to transfer arbitrary amounts of data on a connection, it must implement flow control. See Start a stream, below. To receive messages on a WebSocket connection: func startWebSocketReceive(on connection: NWConnection) { connection.receiveMessage { message, _, _, error in if let error { … handle the error … return } if let message { … handle the incoming message … } startWebSocketReceive(on: connection) } } IMPORTANT WebSocket preserves message boundaries, which is one of the reasons why it’s ideal for your reliable messaging connections. If you use a streaming protocol, like TCP or QUIC streams, you must do your own framing. A good way to do that is with NWProtocolFramer. If you need the metadata associated with the message, get it from the context parameter: connection.receiveMessage { message, context, _, error in … if let message, let metadata = context?.protocolMetadata(definition: NWProtocolWebSocket.definition) as? NWProtocolWebSocket.Metadata { … handle the incoming message and its metadata … } … } Send and receive best effort messages In Multipeer Connectivity, a single session supports both reliable and best effort send modes. In Network framework, a connection is either reliable or best effort, depending on the underlying network protocol. The exact mechanism for sending a message depends on the underlying network protocol. A good protocol for best effort messages is UDP. To send a message on a UDP connection: let connection: NWConnection = … let message: Data = … connection.send(content: message, completion: .idempotent) IMPORTANT UDP datagrams have a theoretical maximum size of just under 64 KiB. However, sending a large datagram results in IP fragmentation, which is very inefficient. For this reason, Network framework prevents you from sending UDP datagrams that will be fragmented. To find the maximum supported datagram size for a connection, gets its maximumDatagramSize property. To receive messages on a UDP connection: func startUDPReceive(on connection: NWConnection) { connection.receiveMessage { message, _, _, error in if let error { … handle the error … return } if let message { … handle the incoming message … } startUDPReceive(on: connection) } } This is exactly the same code as you’d use for WebSocket. Start a stream In Multipeer Connectivity, you can ask the session to start a stream to a specific peer. There are two ways to achieve this in Network framework: If you’re using QUIC for your reliable connection, start a new QUIC stream over that connection. This is one place that QUIC shines. You can run an arbitrary number of QUIC connections over a single QUIC connection group, and QUIC manages flow control (see below) for each connection and for the group as a whole. If you’re using some other protocol for your reliable connection, like WebSocket, you must start a new connection. You might use TCP for this new connection, but it’s not unreasonable to use WebSocket or QUIC. If you need to open a new connection for your stream, you can manage that process over your reliable connection. Choose a protocol to match your send mode explains the general approach for this, although in that case it’s opening a parallel best effort UDP connection rather than a parallel stream connection. The main reason to start a new stream is that you want to send a lot of data to the remote peer. In that case you need to worry about flow control. Flow control applies to both the send and receive side. IMPORTANT Failing to implement flow control can result in unbounded memory growth in your app. This is particularly bad on iOS, where jetsam will terminate your app if it uses too much memory. On the send side, implement flow control by waiting for the connection to call your completion handler before generating and sending more data. For example, on a TCP connection or QUIC stream you might have code like this: func sendNextChunk(on connection: NWConnection) { let chunk: Data = … read next chunk from disk … connection.send(content: chunk, completion: .contentProcessed({ error in if let error { … handle error … return } sendNextChunk(on: connection) })) } This acts like an asynchronous loop. The first send call completes immediately because the connection just copies the data to its send buffer. In response, your app generates more data. This continues until the connection’s send buffer fills up, at which point it defers calling your completion handler. Eventually, the connection moves enough data across the network to free up space in its send buffer, and calls your completion handler. Your app generates another chunk of data For best performance, use a chunk size of at least 64 KiB. If you’re expecting to run on a fast device with a fast network, a chunk size of 1 MiB is reasonable. Receive-side flow control is a natural extension of the standard receive pattern. For example, on a TCP connection or QUIC stream you might have code like this: func receiveNextChunk(on connection: NWConnection) { let chunkSize = 64 * 1024 connection.receive(minimumIncompleteLength: chunkSize, maximumLength: chunkSize) { chunk, _, isComplete, error in if let chunk { … write chunk to disk … } if isComplete { … close the file … return } if let error { … handle the error … return } receiveNextChunk(on: connection) } } IMPORTANT The above is cast in terms of writing the chunk to disk. That’s important, because it prevents unbounded memory growth. If, for example, you accumulated the chunks into an in-memory buffer, that buffer could grow without bound, which risks jetsam terminating your app. The above assumes that you can read and write chunks of data synchronously and promptly, for example, reading and writing a file on a local disk. That’s not always the case. For example, you might be writing data to an accessory over a slow interface, like Bluetooth LE. In such cases you need to read and write each chunk asynchronously. This results in a structure where you read from an asynchronous input and write to an asynchronous output. For an example of how you might approach this, albeit in a very different context, see Handling Flow Copying. Send a resource In Multipeer Connectivity, you can ask the session to send a complete resource, identified by either a file or HTTP URL, to a specific peer. Network framework has no equivalent support for this, but you can implement it on top of a stream: To send, open a stream and then read chunks of data using URLSession and send them over that stream. To receive, open a stream and then receive chunks of data from that stream and write those chunks to disk. In this situation it’s critical to implement flow control, as described in the previous section. Final notes This section collects together some general hints and tips. Concurrency In Multipeer Connectivity, each MCSession has its own internal queue and calls delegate callbacks on that queue. In Network framework, you get to control the queue used by each object for its callbacks. A good pattern is to have a single serial queue for all networking, including your listener and all connections. In a simple app it’s reasonable to use the main queue for networking. If you do this, be careful not to do CPU intensive work in your networking callbacks. For example, if you receive a message that holds JPEG data, don’t decode that data on the main queue. Overriding protocol defaults Many network protocols, most notably TCP and QUIC, are intended to be deployed at vast scale across the wider Internet. For that reason they use default options that aren’t optimised for local networking. Consider changing these defaults in your app. TCP has the concept of a send timeout. If you send data on a TCP connection and TCP is unable to successfully transfer it to the remote peer within the send timeout, TCP will fail the connection. The default send timeout is infinite. TCP just keeps trying. To change this, set the connectionDropTime property. TCP has the concept of keepalives. If a connection is idle, TCP will send traffic on the connection for two reasons: If the connection is running through a NAT, the keepalives prevent the NAT mapping from timing out. If the remote peer is inaccessible, the keepalives fail, which in turn causes the connection to fail. This prevents idle but dead connections from lingering indefinitely. TCP keepalives default to disabled. To enable and configure them, set the enableKeepalive property. To configure their behaviour, set the keepaliveIdle, keepaliveCount, and keepaliveInterval properties. Symbol cross reference If you’re not sure where to start with a specific Multipeer Connectivity construct, find it in the tables below and follow the link to the relevant section. [Sorry for the poor formatting here. DevForums doesn’t support tables properly, so I’ve included the tables as preformatted text.] | For symbol | See | | ----------------------------------- | --------------------------- | | `MCAdvertiserAssistant` | *Discover peers* | | `MCAdvertiserAssistantDelegate` | *Discover peers* | | `MCBrowserViewController` | *Discover peers* | | `MCBrowserViewControllerDelegate` | *Discover peers* | | `MCNearbyServiceAdvertiser` | *Discover peers* | | `MCNearbyServiceAdvertiserDelegate` | *Discover peers* | | `MCNearbyServiceBrowser` | *Discover peers* | | `MCNearbyServiceBrowserDelegate` | *Discover peers* | | `MCPeerID` | *Create a peer identifier* | | `MCSession` | See below. | | `MCSessionDelegate` | See below. | Within MCSession: | For symbol | See | | --------------------------------------------------------- | ------------------------------------ | | `cancelConnectPeer(_:)` | *Manage a connection* | | `connectedPeers` | *Manage a listener* | | `connectPeer(_:withNearbyConnectionData:)` | *Manage a connection* | | `disconnect()` | *Manage a connection* | | `encryptionPreference` | *Plan for security* | | `myPeerID` | *Create a peer identifier* | | `nearbyConnectionData(forPeer:withCompletionHandler:)` | *Discover peers* | | `securityIdentity` | *Plan for security* | | `send(_:toPeers:with:)` | *Send and receive reliable messages* | | `sendResource(at:withName:toPeer:withCompletionHandler:)` | *Send a resource* | | `startStream(withName:toPeer:)` | *Start a stream* | Within MCSessionDelegate: | For symbol | See | | ---------------------------------------------------------------------- | ------------------------------------ | | `session(_:didFinishReceivingResourceWithName:fromPeer:at:withError:)` | *Send a resource* | | `session(_:didReceive:fromPeer:)` | *Send and receive reliable messages* | | `session(_:didReceive:withName:fromPeer:)` | *Start a stream* | | `session(_:didReceiveCertificate:fromPeer:certificateHandler:)` | *Plan for security* | | `session(_:didStartReceivingResourceWithName:fromPeer:with:)` | *Send a resource* | | `session(_:peer:didChange:)` | *Manage a connection* | Revision History 2025-04-11 Added some advice as to whether to use the peer identifier in your service name. Expanded the discussion of how to deduplicate connections in a star network architecture. 2025-03-20 Added a link to the DeviceDiscoveryUI framework to the Discovery UI section. Made other minor editorial changes. 2025-03-11 Expanded the Enable peer-to-peer Wi-Fi section to stress the importance of stopping network operations once you’re done with them. Added a link to that section from the list of Multipeer Connectivity drawbacks. 2025-03-07 First posted.
Replies
0
Boosts
0
Views
1.8k
Activity
Apr ’25
Waze issues
Waze is not available in the home screen icons for selection in CarPlay. I just reloaded Waze when it disappeared as a selectable icon. How can it be restored?
Replies
1
Boosts
0
Views
54
Activity
Apr ’25
"Bug description: We were unable to tap on 'Upgrade to Pro' button."
Hey guys, I am developing a habit tracker app right now. I know it's basic but it's my first app and I wanted an easy start but this is more of a struggle than I thought. So I am trying to publish my app fully on the App Store however everytime I do there is some kind of bug with it. But when I test it, it works perfect for me. Anyways, in this case the reviewer said when they go to the pro page of my app to purchase a pro subscription and try to press upgrade to pro nothing pops up. It pops up normally for me on my iPhone but not other devices. I wanted to make sure if this was normal or if there is something I am supposed to fix. Any help is appreciated. Thank you.
Replies
1
Boosts
0
Views
84
Activity
Mar ’25
Including Place URL in server API response?
Hello, Is it possible to retrieve URLs (the URL associated for the specified location) for the Place object when you query the Apple Maps Server APIs? They are available when you make a MKLocalSearch.Request() directly in Swift. We have both iOS and Android apps, and it's not currently possible to get the URLs on Android. Thanks!
Replies
1
Boosts
0
Views
171
Activity
Apr ’25
Socket Becomes Unresponsive in Local Connectivity Extension After Lock Screen
I’m developing an app designed for hospital environments, where public internet access may not be available. The app includes two components: the main app and a Local Connectivity Extension. Both rely on persistent TCP socket connections to communicate with a local server. We’re observing a recurring issue where the extension’s socket becomes unresponsive every 1–3 hours, but only when the device is on the lock screen, even if the main app remains in the foreground. When the screen is not locked, the connection is stable and no disconnections occur. ❗ Issue Details: • What’s going on: The extension sends a keep-alive ping packet every second, and the server replies with a pong and a system time packet. • The bug: The server stops receiving keep alive packets from the extension.  • On the server, we detect about 30 second gap on the server, a gap that shows no packets were received by the extension. This was confirmed via server logs and Wireshark).  • On the extension, from our logs there was no gap in sending packets. From it’s perspective, all packets were sent with no error.  • Because no packet are being received by the server, no packets will be sent to the extension. Eventually the server closes the connection due to keep-alive timeout.  • FYI we log when the NEAppPushProvider subclass sleeps and it did NOT go to sleep while we were debugging. 🧾 Example Logs: Extension log: 2025-03-24 18:34:48.808 sendKeepAliveRequest() 2025-03-24 18:34:49.717 sendKeepAliveRequest() 2025-03-24 18:34:50.692 sendKeepAliveRequest() ... // continuous sending of the ping packet to the server, no problems here 2025-03-24 18:35:55.063 sendKeepAliveRequest() 2025-03-24 18:35:55.063 keepAliveTimer IS TIME OUT... in CoreService. // this is triggered because we did not receive any packets from the server 2025-03-24 18:34:16.298 No keep-alive received for 16 seconds... connection ID=95b3... // this shows that there has been no packets being received by the extension ... 2025-03-24 18:34:30.298 Connection timed out on keep-alive. connection ID=95b3... // eventually closes due to no packets being received 2025-03-24 18:34:30.298 Remote Subsystem Disconnected {name=iPhone|Replica-Ext|...} ✅ Observations: • The extension process continues running and logging keep-alive attempts. • However, network traffic stops reaching the server, and no inbound packets are received by the extension. • It looks like the socket becomes silently suspended or frozen, without being properly closed or throwing an error. ❓Questions: • Do you know why this might happen within a Local Connectivity Extension, especially under foreground conditions and locked ? • Is there any known system behavior that might cause the socket to be suspended or blocked in this way after running for a few hours? Any insights or recommendations would be greatly appreciated. Thank you!
Replies
1
Boosts
0
Views
87
Activity
Mar ’25
Waiting for HID Entitlements for MONTHS
Hi Apple support, We requested the 4 HID-related Entitlements back in December 2024. Similarly to another post here in the forums that was completely ignored, our request has NOT been processed for months. Mailing the support staff results in boilerplate email responses with no content, calling them results in a chat with very nice people who are unable to help since they can't seem to reach the entitlement team directly. Having to wait for MONTHS when dealing with one of the biggest and supposedly best companies in the world is beyond disappointing. Can anyone help? Is there anyone else that has had this same issue and that has found a work-around? I can share all necessary details. Thanks, Matteo
Replies
1
Boosts
0
Views
168
Activity
Apr ’25
Dext not initializing with a log "Failed to write extension load report plist"
When plugging in my matched USB device I see the logs below. It seems the kernelmanagerd process is sandboxed and can't write out the reason my Dext failed to load. Is there somewhere else I can look for this info? default 11:03:22.175152-0700 kernelmanagerd Received kext load notification: me.keithg.MyUserUSBInterfaceDriver default 11:03:22.177637-0700 kernel 1 duplicate report for Sandbox: icdd(2124) allow file-read-data /Library/Image Capture/Devices error 11:03:22.177681-0700 kernel Sandbox: kernelmanagerd(545) deny(1) file-write-create /private/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/com.apple.kernelmanagerd/TemporaryItems com.apple.libcoreservices error 11:03:22.177711-0700 kernelmanagerd mkdir: path=/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/com.apple.kernelmanagerd/TemporaryItems/ mode= -rwx------: [1: Operation not permitted] error 11:03:22.179361-0700 kernel Sandbox: kernelmanagerd(545) deny(1) file-write-create /private/var/db/loadedkextmt.plist.sb-5a00fc77-LNttZF com.apple.libcoreservices error 11:03:22.177755-0700 kernelmanagerd _dirhelper_relative_internal: error for path <private>: [1: Operation not permitted] com.apple.accessories default 11:03:22.177674-0700 WindowServer Sending analytics event... (eventName: com.apple.ioport.transport.USB.published) error 11:03:22.179913-0700 kernelmanagerd Failed to write extension load report plist.
Replies
1
Boosts
0
Views
265
Activity
Mar ’25