{"id":56525,"date":"2025-04-22T20:46:29","date_gmt":"2025-04-23T03:46:29","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=56525"},"modified":"2025-04-29T13:16:53","modified_gmt":"2025-04-29T20:16:53","slug":"building-real%e2%80%91time-ios-apps-with-signalr-introducing-the-official-swift-client-public-preview","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/building-real%e2%80%91time-ios-apps-with-signalr-introducing-the-official-swift-client-public-preview\/","title":{"rendered":"Building Real\u2011Time iOS Apps with SignalR: Introducing the Official Swift Client (Public Preview)"},"content":{"rendered":"<h2 data-start=\"363\" data-end=\"379\">Introduction<\/h2>\n<p class=\"\" data-start=\"381\" data-end=\"738\">Until now, iOS developers who wanted real\u2011time, bi\u2011directional communication with SignalR had to rely on community\u2011built clients or roll their own Swift implementation\u2014both of which introduced maintenance and compatibility headaches. We\u2019re excited to announce that the <strong data-start=\"656\" data-end=\"695\">official SignalR Swift client<\/strong> is now available in <strong data-start=\"716\" data-end=\"734\">public preview.<\/strong><\/p>\n<p class=\"\" data-start=\"740\" data-end=\"767\">With this release, you can:<\/p>\n<ul>\n<li data-start=\"771\" data-end=\"877\">Quickly add real\u2011time features (chat, notifications, live dashboards) to your SwiftUI or UIKit apps.<\/li>\n<li data-start=\"880\" data-end=\"973\">Leverage full SignalR functionality\u2014hubs, groups, client\/server streaming\u2014on iOS and macOS.<\/li>\n<li data-start=\"976\" data-end=\"1040\">Rely on an officially supported, maintained, and tested library.<\/li>\n<\/ul>\n<p>In this post you\u2019ll learn how to set up the Swift client and use its core features. During a recent .NET Community Standup, we demoed an AI-enabled chat sample that uses SignalR for streaming AI\u2011generated tokens to iOS clients. We recommend you having a watch. You can find the repo referenced in the episode at the end of this blog post.<\/p>\n<p><iframe src=\"\/\/www.youtube.com\/embed\/LHN0yrm8ADs?list=PLdo4fOcmZ0oX-DBuRG4u58ZTAJgBAeQ-t&amp;index=7\" width=\"560\" height=\"314\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<h2>How to set up Swift client and use its core features<\/h2>\n<h3>Requirements<\/h3>\n<ul>\n<li>Swift\u00a0<strong>&gt;= 5.10<\/strong><\/li>\n<li>macOS\u00a0<strong>&gt;= 11.0<\/strong><\/li>\n<li>iOS &gt;= 14<\/li>\n<\/ul>\n<h3>Install via Swift Package Manager<\/h3>\n<p>Add the SignalR Swift package as a dependency in your\u00a0<code>Package.swift<\/code>\u00a0file:<\/p>\n<pre><code>\/\/ swift-tools-version: 5.10 \r\n<\/code><code>import PackageDescription \r\n<\/code><code>let package = Package( \r\n<\/code><code>   name: \"signalr-client-app\", \r\n<\/code><code>    dependencies: [ \r\n<\/code><code>           .package(url: \"https:\/\/github.com\/dotnet\/signalr-client-swift\", branch: \"main\") \r\n<\/code><code>        ], \r\n<\/code><code>        targets: [ \r\n<\/code><code>            .executableTarget(name: \"YourTargetName\", dependencies: [.product(name: \"SignalRClient\", package: \"signalr-client-swift\")]) \r\n<\/code><code>        ] \r\n<\/code><code>)<\/code><\/pre>\n<p>After adding the dependency, import the library in your Swift code:<\/p>\n<h3>Establish a Hub Connection<\/h3>\n<p>Create and start your connection:<\/p>\n<pre><code>let connection = HubConnectionBuilder()\r\n<\/code><code>    .withUrl(url: \"https:\/\/your-signalr-server\/hub\")\r\n<\/code><code>    .build()\r\n<\/code><code>\r\ntry await connection.start()<\/code><\/pre>\n<p><strong data-start=\"1569\" data-end=\"1577\">Tip:<\/strong> Register your handlers <strong data-start=\"1601\" data-end=\"1611\">before<\/strong> calling <code data-start=\"1620\" data-end=\"1629\">start()<\/code> to avoid missing early messages.<\/p>\n<p>After adding the dependency, import the library in your Swift code:<\/p>\n<pre class=\"has-inner-focus\"><code class=\"lang-swift\"><span class=\"hljs-keyword\">import<\/span> SignalRClient<\/code><\/pre>\n<h3>Receive Server Calls<\/h3>\n<p class=\"\" data-start=\"1697\" data-end=\"1742\">To handle messages from the server, use <code data-start=\"1737\" data-end=\"1741\">on<\/code>:<\/p>\n<pre><code>await connection.on(\"ReceiveMessage\") { (user: String, msg: String) in\r\n<\/code><code>  \u00a0 \u00a0 print(\"\\(user): \\(msg)\")\r\n}<\/code><\/pre>\n<p class=\"\" data-start=\"1860\" data-end=\"1877\">On your .NET hub:<\/p>\n<pre data-start=\"1697\" data-end=\"1742\"><code>public class ChatHub : Hub\r\n{\r\n    public async Task SendMessage(string user, string message) =&gt;\r\n        await Clients.All.SendAsync(\"ReceiveMessage\", user, message);\r\n}<\/code><\/pre>\n<p class=\"\" data-start=\"2061\" data-end=\"2111\">The method name and parameters must match exactly.<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<h3 class=\"overflow-y-auto p-4\" dir=\"ltr\">Invoke vs. Send<\/h3>\n<p><strong data-start=\"2143\" data-end=\"2155\"><code data-start=\"2145\" data-end=\"2153\">invoke<\/code><\/strong> waits for the server response (and throws on error):<\/p>\n<pre><code>let result: String = try await connection.invoke(\r\n    <\/code><code>method: \"Calculate\", arguments: 42\r\n)<\/code><\/pre>\n<p><strong data-start=\"2324\" data-end=\"2334\"><code data-start=\"2326\" data-end=\"2332\">send<\/code><\/strong> fires and forgets:<\/p>\n<pre><code>try await connection.send(\r\n<\/code><code> \u00a0  method: \"SendMessage\", arguments: \"Alice\", \"Hello!\"\r\n)<\/code><\/pre>\n<h3>Stream Server Data<\/h3>\n<p>For large or incremental data, use <code data-start=\"2528\" data-end=\"2536\">stream<\/code>:<\/p>\n<pre><code>let streamResult: any StreamResult&lt;String&gt; =\r\n<\/code><code>    try await connection.stream(method: \"StreamNumbers\")\r\n<\/code><code>    for try await number in streamResult.stream {\r\n<\/code><code>        print(\"Received number: \\(number)\")\r\n    }<\/code><\/pre>\n<p data-start=\"2744\" data-end=\"2797\">On the server you\u2019d implement a streaming hub method:<\/p>\n<pre data-start=\"2744\" data-end=\"2797\"><code>public async IAsyncEnumerable&lt;int&gt; StreamNumbers()<\/code><code>\r\n{<\/code><code>   for (var i = 0; i &lt; 10; i++)<\/code><code>{\r\n        <\/code><code>yield return i;<\/code> <code>await Task.Delay(500);\r\n    <\/code><code>}\r\n<\/code><code>}<\/code><\/pre>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<h3 class=\"overflow-y-auto p-4\" dir=\"ltr\">Automatic Reconnect<\/h3>\n<p class=\"\" data-start=\"3001\" data-end=\"3029\">Enable built\u2011in retry logic:<\/p>\n<pre data-start=\"3001\" data-end=\"3029\"><code>let connection = HubConnectionBuilder()\r\n    <\/code><code>.withUrl(url: \"https:\/\/your-signalr-server\/hub\")\r\n    <\/code><code>.withAutomaticReconnect()\r\n    <\/code><code>.build()<\/code><\/pre>\n<\/div>\n<p>By default, a client waits 0, 2, 10, and 30 seconds before making reconnect attempts. Customize delays or supply a <code data-start=\"3241\" data-end=\"3254\">RetryPolicy<\/code> for more control. Use <code data-start=\"3277\" data-end=\"3293\">onReconnecting<\/code> or <code data-start=\"3296\" data-end=\"3311\">onReconnected<\/code> to update UI or state.<\/p>\n<h3>Logging &amp; Diagnostics<\/h3>\n<p class=\"\" data-start=\"3370\" data-end=\"3397\">Control log verbosity with:<\/p>\n<pre data-start=\"3370\" data-end=\"3397\"><code>HubConnectionBuilder()\r\n<\/code><code>    .withLogLevel(.debug) \/\/ or .information, .warning, .error\r\n<\/code><code>    .build()<\/code><\/pre>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<h3 class=\"overflow-y-auto p-4\" dir=\"ltr\">Transport Configuration<\/h3>\n<p class=\"\" data-start=\"3644\" data-end=\"3729\">By default, the client prefers WebSockets, then SSE, then long polling. Override this behavior with:<\/p>\n<pre data-start=\"3644\" data-end=\"3729\"><code>.withUrl(url: \"https:\/\/\u2026\", transport: [.webSockets])<\/code><\/pre>\n<\/div>\n<h2 class=\"\" data-start=\"3803\" data-end=\"3816\">Next Steps<\/h2>\n<ul>\n<li data-start=\"3820\" data-end=\"3933\">Learn more about configuration options available in the <a href=\"https:\/\/learn.microsoft.com\/en-us\/aspnet\/core\/signalr\/swift-client?view=aspnetcore-9.0\">Swift client<\/a>.<\/li>\n<li data-start=\"3820\" data-end=\"3933\">Explore<strong data-start=\"3828\" data-end=\"3848\">\u00a0samples<\/strong> in the <a class=\"\" href=\"https:\/\/github.com\/dotnet\/signalr-client-swift\" target=\"_new\" rel=\"noopener\" data-start=\"3856\" data-end=\"3931\">signalr-client-swift repo<\/a>, including the AI chat sample we mentioned earlier.<\/li>\n<li data-start=\"3936\" data-end=\"4001\">Connect to <strong data-start=\"3947\" data-end=\"3972\">Azure SignalR Service<\/strong> or your own SignalR server<\/li>\n<\/ul>\n<p class=\"\" data-start=\"4071\" data-end=\"4192\">We\u2019re eager to hear your feedback\u2014open an issue or PR on GitHub and help us refine the Swift client as we move toward GA.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Until now, iOS developers who wanted real\u2011time, bi\u2011directional communication with SignalR had to rely on community\u2011built clients or roll their own Swift implementation\u2014both of which introduced maintenance and compatibility headaches. We\u2019re excited to announce that the official SignalR Swift client is now available in public preview. With this release, you can: Quickly add real\u2011time [&hellip;]<\/p>\n","protected":false},"author":139884,"featured_media":56616,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7812,7509],"tags":[],"class_list":["post-56525","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-dotnet-ios","category-aspnetcore"],"acf":[],"blog_post_summary":"<p>Introduction Until now, iOS developers who wanted real\u2011time, bi\u2011directional communication with SignalR had to rely on community\u2011built clients or roll their own Swift implementation\u2014both of which introduced maintenance and compatibility headaches. We\u2019re excited to announce that the official SignalR Swift client is now available in public preview. With this release, you can: Quickly add real\u2011time [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/56525","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/139884"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=56525"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/56525\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/56616"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=56525"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=56525"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=56525"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}