{"id":1468,"date":"2024-03-05T22:22:54","date_gmt":"2024-03-05T22:22:54","guid":{"rendered":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/"},"modified":"2024-03-05T22:22:54","modified_gmt":"2024-03-05T22:22:54","slug":"how-we-built-the-deals-tab-in-swift-ui","status":"publish","type":"post","link":"https:\/\/dev95.site\/ar\/how-we-built-the-deals-tab-in-swift-ui\/","title":{"rendered":"How We Built The Deals Tab in Swift UI"},"content":{"rendered":"<div id=\"dev95-1807232430\" class=\"dev95-- dev95-entity-placement\"><script async=\"async\" data-cfasync=\"false\" src=\"https:\/\/pl27862732.profitableratecpmnetwork.com\/2ad7a50e0bbc23ac6801d7b77c501463\/invoke.js\"><\/script>\r\n<div id=\"container-2ad7a50e0bbc23ac6801d7b77c501463\"><\/div><\/div><div>\n<h1><em>Balancing Engineering Ambition with Product Realism<\/em><\/h1>\n<h2>Introduction<\/h2>\n<p>In July of 2023, Etsy\u2019s App Updates team, responsible for the Updates feed in Etsy\u2019s mobile apps, set off with an ambitious goal: to revamp the Updates tab to become Deals, a home for a shopper\u2019s coupons and sales, in time for Cyber Week 2023.<\/p>\n<p>The Updates tab had been around for years, and in the course of its evolution ended up serving multiple purposes. It was a hub for updates about a user\u2019s favorite shops and listings, but it was also increasingly a place to start new shopping journeys. Not all updates were created equal. The most acted-upon updates in the tab were coupons offered for abandoned cart items, which shoppers loved.<\/p><div id=\"dev95-2003227101\" class=\"dev95- dev95-entity-placement\"><center>\r\n<script>\r\n  atOptions = {\r\n    'key' : '4ba6b6513c00e0ba76511f798ae56401',\r\n    'format' : 'iframe',\r\n    'height' : 50,\r\n    'width' : 320,\r\n    'params' : {}\r\n  };\r\n<\/script>\r\n<script src=\"https:\/\/www.highrevenueformat.com\/4ba6b6513c00e0ba76511f798ae56401\/invoke.js\"><\/script>\r\n\t<\/center><\/div>\n<p>We spotted an opportunity to clarify intentions for our users: by refactoring favorite-based updates into the Favorites tab, and (more boldly), by recentering Updates and transforming it into a hub for a buyer\u2019s deals.<\/p>\n<h2>Technical Opportunity<\/h2>\n<p>While investigating the best way to move forward with the Deals implementation, iOS engineers on the team advocated for developing a new tab from the ground up. Although it meant greater initial design and architecture effort, an entirely new tab built on modern patterns would let us avoid relying on Objective C, as well as internal frameworks like SDL (server-driven layout), which is present in many legacy Etsy app screens and comes with a variety of scalability and performance issues, and is in the process of being phased out. <\/p>\n<p>At the same time, we needed a shippable product by October. Black Friday and Cyber Week loomed on the horizon in November, and it would be a missed opportunity, for us and for our users, not to have the Deals tab ready to go. Our ambition to use modern, not yet road-tested technologies would have to balance with realism about the needs of the product, and we were conscious of maintaining that balance throughout the course of development.<\/p>\n<h2>In comes Swift UI and Tuist!<\/h2>\n<p>Two new frameworks were front of mind when starting this project: <a href=\"https:\/\/developer.apple.com\/xcode\/swiftui\/\">Swift UI<\/a> and <a href=\"https:\/\/tuist.io\/\">Tuist<\/a>. Swift UI provides a clear, declarative framework for UI development, and makes it easy for engineers to break down views into small, reusable components. Maybe Swift UI\u2019s biggest benefit is its built-in view previews: in tandem with componentization, it becomes a very straightforward process to build a view out of smaller pieces and preview at every step of the way. <\/p>\n<p>Our team had experimented with Swift UI in the past, but with scopes limited to small views, such as headers. Confident as we were about the framework, we expected that building out a whole screen in Swift UI would present us some initial hurdles to overcome. In fact, one hurdle presented itself right away. In a decade-old codebase, not everything is optimized for use with newer technologies. The build times we saw for our Swift UI previews, which were almost long enough to negate the framework\u2019s other benefits, testified to that fact.<\/p>\n<p>This is where Tuist comes in. Our App Enablement team, which has been hard at work over the past few years modernizing the Etsy codebase, has adopted Tuist as a way of taming the monolith making it modular. Any engineer at Etsy can declare a Tuist module in their project and start working on it, importing parts of the larger codebase only as they need them. (For more on Etsy\u2019s usage of Tuist, check out <a href=\"https:\/\/tuist.io\/blog\/2023\/11\/30\/etsy-and-tuist\/\">this article<\/a> by Mike Simons from the App Enablement team.) Moving our work for the Deals tab into a Swift-based Tuist module gave us what it took to make a preview-driven development process practical: our previews build nearly instantly, and so long as we\u2019re only making changes in our framework the app recompiles with very little delay.<\/p>\n<figure>\n<img data-recalc-dims=\"1\" decoding=\"async\" alt=\"Deals UI Framework Diagram\" src=\"https:\/\/i0.wp.com\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?w=1280&#038;ssl=1\" title=\"Deals UI Framework Diagram\"><figcaption><strong>Figure 1.<\/strong> A view of a goal end state of a modular Etsy codebase, with a first layer of core modules (in blue), and a second layer of client-facing modules that combine to build the Etsy app.<\/figcaption><\/figure>\n<h2>Our architecture<\/h2>\n<p>The Deals tab comprises a number of modules for any given Etsy user, including a Deals Just for You module with abandoned cart coupons, and a module that shows a user their favorite listings that are on sale. Since the screen is just a list of modules, the API returns them as an array of typed items with the following structure:<\/p>\n<pre><code>{\n    \"type\": \"&lt;ModuleName&gt;\",\n    \"&lt;ModuleName&gt;\": { ... }\n}<\/code><\/pre>\n<p>Assigning each module a type enables us to parse it correctly on the client, and moves us away from the anonymous component-based API models we had used in the past. Many models are still used across modules, however. These include, but are not limited to, buttons, headers and footers, and listing cards. To parse a new module, we either have to build a new component if it doesn&#8217;t exist yet, or reuse one that does. Adding a footer to a module, for example, can be as simple as:<\/p>\n<pre><code>\/\/ Model\n{\n    \"type\": \"my_module\",\n    \"my_module\": {\n        \"target_listing\": { &lt;ListingCard&gt; }\",\n        \"recommended_listings\": [ &lt;ListingCard&gt; ],\n        \"footer\": { &lt;Footer&gt; } \/\/ Add footer here\n    }\n}\n\n\/\/ View \nvar body: some View {\n    VStack {\n        ListingView(listing: targetListing)\n        ListingCarouselView(listings: recommendedListings)\n        MyFooterView(footer: footer) \/\/ Add footer here\n    }\n}<\/code><\/pre>\n<p>We also used Decodable implementations for our API parsing, leading to faster, clearer code and an easier way to handle optionals. With Etsy\u2019s internal <em>APIv3<\/em> framework built on top of Apple\u2019s Decodable protocol, it is very straightforward to define a model and decide what is and isn\u2019t optional, and let the container handle the rest. For example, if the footer was optional, but the target and recommended listings are required, decoding would look like this:<\/p>\n<pre><code>init(from decoder: Decoder) throws {\n    let container = try decoder.containerV3(keyedBy: CodingKeys.self)\n\n    \/\/ These will throw if they aren't included in the response\n    self.targetListing = try container.requireV3(forKey: .targetListing)\n    self.recommendedListings = try container.requireV3(forKey: .recommendedListings)\n\n    \/\/ Footer is optional\n    self.footer = container.decodeV3(forKey: .footer) \n}<\/code><\/pre>\n<p>As for laying out the view on the screen, we used a Swift UI <em>List<\/em> to make the most of the under-the-hood cell reuse that <em>List<\/em> uses.<\/p>\n<figure>\n<img data-recalc-dims=\"1\" decoding=\"async\" alt=\"Deals Hosting Controller Diagram\" src=\"https:\/\/i0.wp.com\/i.etsystatic.com\/inv\/fda52c\/5865924623\/inv_fullxfull.5865924623_5o6kigqf.jpg?w=1280&#038;ssl=1\" title=\"Deals Hosting Controller Diagram\"><figcaption><strong>Figure 2.<\/strong> On the left-hand side, a diagram of how the DealsUI view is embedded in the Etsy app. On the right-hand side, a diagram of how the DeasUI framework takes the API response and renders a list of module views with individual components.<\/figcaption><\/figure>\n<h2>Previews, previews, more previews<\/h2>\n<p>If we were going to maintain a good development pace, we needed to figure out a clean way to use Swift previews. Previewing a small component, like a header that takes a string, is simple enough: just initialize the header view with the header string. For more complex views, though, it gets cumbersome to build a mock API response every time you need to preview. This complexity is only amplified when previewing an entire Deals module.<\/p>\n<p>To streamline the process, we decided to add a <code>Previews<\/code> enum to our more complex models. A good example of this is in the Deals Just for You coupon cards. These cards display an image or an array of images, a few lines of custom text (depending on the coupon type), and a button. Our previews enum for this API model looks like this:<\/p>\n<pre><code>\/\/ In an extension to DealsForYouCard\n\nenum Previews {\n    static var shopCouponThreeImage: ResponseModels.DealsForYouCard {\n        let titleText = \"IrvingtonWoodworksStudio\"\n        let images = [...] \/\/ Three images\n        let button = ResponseModels.Button(\n            buttonText: \"10% off shop\",\n            action: .init(...)\n        )\n\n        return ResponseModels.DealsForYouCard(\n            button: button, \n            saleBadge: \"20% off\", \n            titleText: titleText,\n            subtitleText: \"Favorited shop\", \n            action: .init(...), \n            images: images\n        )\n    }\n\n    static var listingCoupon: ResponseModels.DealsForYouCard {\n        ...    \n    }\n}<\/code><\/pre>\n<p>Then, previewing a variety of coupon cards, it\u2019s as straightforward as:<\/p>\n<pre><code>#Preview {\n    DealsForYouCardView(coupon: .Previews.listingCoupon)\n}\n\n#Preview {\n    DealsForYouCardView(coupon: .Previews.shopCouponThreeImage)\n}<\/code><\/pre>\n<p>The other perk of this architecture is that it makes it very easy to nest previews, for example when previewing an entire module. To build preview data for the <em>Deals for You<\/em> module, we can use our coupon examples this way:<\/p>\n<pre><code>\/\/ In an extension to DealsForYouModule\nenum Previews {\n    static var mockModule: ResponseModels.DealsForYouModule {\n        let items: [ResponseModels.DealsForYouCard] = [.Previews.listingCoupon, .Previews.shopCouponThreeImage, .Previews.shopCouponTwoImage]\n        let header = ResponseModels.DealsForYouHeader(title: \"Deals just for you\")\n\n        return .init(header: header, items: items)\n    }\n}<\/code><\/pre>\n<p>These enums are brief, clear, and easy to understand; they allow us to lean into the benefits of modularity. <\/p>\n<p>This architecture, along with our Decodable models, also enabled us to clear a roadblock that used to occur when our team had to wait for API support before we could build new modules. For example, both the <em>Similar Items on Sale<\/em> and <em>Extra Special Deals<\/em> modules in the Deals tab were built via previews, and were ready approximately two weeks before the corresponding API work was complete, helping us meet deadlines and not have to wait for a new App Store release.<\/p>\n<p>By taking full advantage of Swift UI&#8217;s modularity and previewability, not only were we able to prove out a set of new technologies, we also exceeded product expectations by significantly beating our deadlines even with the initial overhead of adopting the framework.<\/p>\n<h2>Challenges: UIKit interoperability<\/h2>\n<p>Particularly when it came to tasks like navigation and favoriting, interfacing between our module and the Etsy codebase could pose challenges. An assumption that we had as engineers going into this project was that the code to open a listing page, for example, would just be readily available to use; this was not the case, however. Most navigation code within the Etsy codebase is handled by an Objective C class called EtsyScreenController. While in the normal target, it\u2019s as straightforward as calling a function to open a listing page, that functionality was not available to us in our Deals module.<\/p>\n<p>One option would have been to build our own navigation logic using Swift UI Navigation stacks; we weren\u2019t trying to reinvent the wheel, however. To balance product deadlines and keep things as simple as possible, we decided not to be dogmatic, and to handle navigation outside of our framework. We did this by building a custom <code>@Environment<\/code> struct, called <code>DealsAction<\/code>, which passes off responsibility for navigation back to the main target, and uses the new Swift <code>callAsFunction()<\/code> <a href=\"https:\/\/www.hackingwithswift.com\/swift\/5.2\/callasfunction\">feature<\/a> so we can treat this struct like a function in our views. <\/p>\n<p>We have a concept of a <code>DealsAction<\/code> type in our API response, which enables us to match an action with an actionable part of the screen. For example, a button response has an action that will be executed when a user taps the button. The DealsAction handler takes that action, and uses our existing UIKit code to perform it. <\/p>\n<p>The Deals tab is wrapped in a <code>UIHostingController<\/code> in the main Etsy target, so when setting up the Swift UI view, we also set the <code>DealsAction<\/code> environment object using a custom view modifier:<\/p>\n<pre><code>let dealsView = DealsView()\n                .handleDealsAction {\n                    [weak self] in self?.handleAction(action: $0)\n                }\n\n...\n\nfunc handleDealsAction(action: DealsAction) {\n    \/\/ UIKit code to execute action\n}<\/code><\/pre>\n<p>Then, when we need to perform an action from a Swift UI view, the action handler is present at any layer within the view hierarchy within the Deals tab. Performing the action is as simple as:<\/p>\n<pre><code>@Environment(.handleDealsAction) var handleDealsAction: DealsAction\n...\nMyButton(title: buttonText, fillWidth: false) {\n    handleDealsAction(model.button?.action)\n}<\/code><\/pre>\n<p>We reused this pattern for other existing functionality that was only available in the main target. For example, we built an environment object for favoriting listings, or for following a shop, and for logging performance metrics. This pattern allows us to include environment objects as needed, and it simplifies adding action handling to any view. Instead of rebuilding this functionality in our Tuist module in pure Swift, which could have taken multiple sprints, we struck a balance between modernization and the need to meet product deadlines.<\/p>\n<h2>Challenges: Listing Cards<\/h2>\n<p>The listing card view is a common component used across multiple screens within the Etsy app. This component was originally written in Objective-C and throughout the years has been enhanced to support multiple configurations and layouts, and to be available for A\/B testing. It also has built-in functionality like favoriting, which engineers shouldn&#8217;t have to reimplement each time they want to present a listing card.<\/p>\n<figure>\n<img data-recalc-dims=\"1\" decoding=\"async\" alt=\"Listing Card Configuration\" src=\"https:\/\/i0.wp.com\/i.etsystatic.com\/inv\/b069d7\/5817867384\/inv_fullxfull.5817867384_15cbo86b.jpg?w=1280&#038;ssl=1\" title=\"Listing Card Configuration\"><figcaption><strong>Figure 3.<\/strong> A diagram of how listing card views are conventionally built in UIKit, using configuration options and the analytics framework to combine various UIKit subviews.<\/figcaption><\/figure>\n<p>It&#8217;s been our practice to reuse this same single component and make small modifications to support changes in the UI, as per product or experimentation requirements. This means that many functionalities, such as favoriting, long-press menus, and image manipulation, are heavily coupled with this single component, many parts of which are still written in Objective C.<\/p>\n<p>Early in the process of developing the new tab, we decided to scope out a way of supporting conventional listing card designs\u2014ones that matched existing cards elsewhere in the app\u2014without having to rebuild the entire card component in Swift UI. We knew a rebuild would eventually be necessary, since we expected to have to support listing cards that differed significantly from the standard designs, but the scope of such a rebuild was a known unknown. To balance our deadlines, we decided to push this more ambitious goal until we knew we had product bandwidth.<\/p>\n<p>Since the listing card view is heavily coupled with old parts of the codebase, however, it wasn\u2019t as simple as just typing <code>import ListingCard<\/code> and flying along. We faced two challenges: first, the API model for a listing card couldn\u2019t be imported into our module, and second the view couldn\u2019t be imported for simple use in a <code>UIViewRepresentable<\/code> wrapper. To address these, we deferred responsibility back up to the UIKit view controller. Our models for a listing card component look something like this:<\/p>\n<pre><code>struct ListingCard {\n    public let listingCardWrapper: ListingCardWrapper\n\n    let listingCard: TypedListingCard\n}<\/code><\/pre>\n<p>The model is parsed in two ways: as a wrapper, where it is parsed as an untyped dictionary that will eventually be used to initialize our legacy listing card model, and as a <code>TypedListingCard<\/code>, which is used only within the Deals tab module.<\/p>\n<figure>\n<img data-recalc-dims=\"1\" decoding=\"async\" alt=\"Deals Card Builder pattern\" src=\"https:\/\/i0.wp.com\/i.etsystatic.com\/inv\/d036b7\/5817868364\/inv_fullxfull.5817868364_4wx1xll9.jpg?w=1280&#038;ssl=1\" title=\"Deals Card Builder pattern\"><figcaption><strong>Figure 4.<\/strong> A diagram of how a UIKit listing card builder is passed from the main target to the Deals framework for rendering listing cards.<\/figcaption><\/figure>\n<p>To build the listing card view, we pass in a view builder to the SwiftUI <code>DealsView<\/code> initializer in the hosting controller code. Here, we are in the full Etsy app codebase, meaning that we have access to the legacy listing card code. When we need to build a listing card, we use this view builder as follows:<\/p>\n<pre><code>var body: some View {\n    LazyVGrid(...) {\n        ForEach(listings) { listing in\n            cardViewBuilder(listing) \/\/ Returns a UIViewRepresentable\n        }\n    }\n}<\/code><\/pre>\n<p>There was some initial overhead involved in getting these cards set up, but it was worth it to guarantee that engineering unknowns in a Swift UI rewrite wouldn\u2019t block us and compromise our deadlines. Once built, the support for legacy cards enabled us to reuse them easily wherever they were needed. In fact, legacy support was one of the things that helped us move faster than we expected, and it became possible to stretch ourselves and build at least some listing cards in the Deals tab entirely in Swift UI. This meant that writing the wrapper ultimately gave us the space we needed to avoid having to rely solely on the wrapper! <\/p>\n<h2>Conclusion<\/h2>\n<p>After just three months of engineering work, the Deals tab was built and ready to go, even beating product deadlines. While it took some engineering effort to overcome initial hurdles, as well as the switch in context from working in UIKit in the main target to working in Swift UI in our own framework, once we had solutions to those challenges and could really take advantage of the new architecture, we saw a very substantial increase in our engineering velocity.<\/p>\n<p>Instead of taking multiple sprints to build, new modules could take just one sprint or less; front-end work was decoupled from API work using Previews, which meant we no longer had to wait for mock responses or even API support at all; and maybe most important, it was fun to use Swift UI\u2019s clear and straightforward declarative UI building, and see our changes in real time!<\/p>\n<p>From a product perspective, the Deals tab was a great success: buyers converted their sessions more frequently, and we saw an increase in visits to the Etsy app. The tab was rolled out to all users in mid October, and has seen significant engagement, particularly during Black Friday and Cyber Monday.<\/p>\n<p>By being bold and by diving confidently into new frameworks that we expected to see benefits from, we improved engineer experience and not just met but beat our product deadlines. More teams at Etsy are using Swift UI and Tuist in their product work now, thanks to the success of our undertaking, sometimes using our patterns to work through hurdles, sometimes creating their own. We expect to see more of this kind of modernization start to make its way into the codebase. As we iterate on the Deals tab over the next year, and make it even easier for buyers to find the deals that mean the most to them, we look forward to continuing to work in the same spirit.<\/p>\n<p><em>Special thanks to Vangeli Ontiveros for the diagrams in this article, and a huge shoutout to the whole App Deals team for their hard work on this project!<\/em><\/p>\n<\/div>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_1468\" class=\"pvc_stats total_only  \" data-element-id=\"1468\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" version=\"1.0\" viewBox=\"0 0 502 315\" preserveAspectRatio=\"xMidYMid meet\"><g transform=\"translate(0,332) scale(0.1,-0.1)\" fill=\"\" stroke=\"none\"><path d=\"M2394 3279 l-29 -30 -3 -207 c-2 -182 0 -211 15 -242 39 -76 157 -76 196 0 15 31 17 60 15 243 l-3 209 -33 29 c-26 23 -41 29 -80 29 -41 0 -53 -5 -78 -31z\"\/><path d=\"M3085 3251 c-45 -19 -58 -50 -96 -229 -47 -217 -49 -260 -13 -295 52 -53 146 -42 177 20 16 31 87 366 87 410 0 70 -86 122 -155 94z\"\/><path d=\"M1751 3234 c-13 -9 -29 -31 -37 -50 -12 -29 -10 -49 21 -204 19 -94 39 -189 45 -210 14 -50 54 -80 110 -80 34 0 48 6 76 34 21 21 34 44 34 59 0 14 -18 113 -40 219 -37 178 -43 195 -70 221 -36 32 -101 37 -139 11z\"\/><path d=\"M1163 3073 c-36 -7 -73 -59 -73 -102 0 -56 133 -378 171 -413 34 -32 83 -37 129 -13 70 36 67 87 -16 290 -86 209 -89 214 -129 231 -35 14 -42 15 -82 7z\"\/><path d=\"M3689 3066 c-15 -9 -33 -30 -42 -48 -48 -103 -147 -355 -147 -375 0 -98 131 -148 192 -74 13 15 57 108 97 206 80 196 84 226 37 273 -30 30 -99 39 -137 18z\"\/><path d=\"M583 2784 c-38 -19 -67 -74 -58 -113 9 -42 211 -354 242 -373 16 -10 45 -18 66 -18 51 0 107 52 107 100 0 39 -1 41 -124 234 -80 126 -108 162 -133 173 -41 17 -61 16 -100 -3z\"\/><path d=\"M4250 2784 c-14 -9 -74 -91 -133 -183 -95 -150 -107 -173 -107 -213 0 -55 33 -94 87 -104 67 -13 90 8 211 198 130 202 137 225 78 284 -27 27 -42 34 -72 34 -22 0 -50 -8 -64 -16z\"\/><path d=\"M2275 2693 c-553 -48 -1095 -270 -1585 -649 -135 -104 -459 -423 -483 -476 -23 -49 -22 -139 2 -186 73 -142 361 -457 571 -626 285 -228 642 -407 990 -497 242 -63 336 -73 660 -74 310 0 370 5 595 52 535 111 1045 392 1455 803 122 121 250 273 275 326 19 41 19 137 0 174 -41 79 -309 363 -465 492 -447 370 -946 591 -1479 653 -113 14 -422 18 -536 8z m395 -428 c171 -34 330 -124 456 -258 112 -119 167 -219 211 -378 27 -96 24 -300 -5 -401 -72 -255 -236 -447 -474 -557 -132 -62 -201 -76 -368 -76 -167 0 -236 14 -368 76 -213 98 -373 271 -451 485 -162 444 86 934 547 1084 153 49 292 57 452 25z m909 -232 c222 -123 408 -262 593 -441 76 -74 138 -139 138 -144 0 -16 -233 -242 -330 -319 -155 -123 -309 -223 -461 -299 l-81 -41 32 46 c18 26 49 83 70 128 143 306 141 649 -6 957 -25 52 -61 116 -79 142 l-34 47 45 -20 c26 -10 76 -36 113 -56z m-2057 25 c-40 -58 -105 -190 -130 -263 -110 -324 -59 -707 132 -981 25 -35 42 -64 37 -64 -19 0 -241 119 -326 174 -188 122 -406 314 -532 468 l-58 71 108 103 c185 178 428 349 672 473 66 33 121 60 123 61 2 0 -10 -19 -26 -42z\"\/><path d=\"M2375 1950 c-198 -44 -350 -190 -395 -379 -18 -76 -8 -221 19 -290 114 -284 457 -406 731 -260 98 52 188 154 231 260 27 69 37 214 19 290 -38 163 -166 304 -326 360 -67 23 -215 33 -279 19z\"\/><\/g><\/svg><\/i> <img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/i0.wp.com\/dev95.site\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif?resize=16%2C16&#038;ssl=1\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Balancing Engineering Ambition with Product Realism Introduction In July of 2023, Etsy\u2019s App Updates team, responsible for the Updates feed in Etsy\u2019s mobile apps, set off with an ambitious goal: to revamp the Updates tab to become Deals, a home<\/p>\n<div class=\"hosteria-entry-more\"><a href=\"https:\/\/dev95.site\/ar\/how-we-built-the-deals-tab-in-swift-ui\/\" class=\"no-underline font-light  group-hover:text-primary-800 dark:group-hover:text-primary-300 py-1\">Read more &gt;&gt;&gt;<\/a><\/div>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_1468\" class=\"pvc_stats total_only\" data-element-id=\"1468\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" version=\"1.0\" viewbox=\"0 0 502 315\" preserveaspectratio=\"xMidYMid meet\"><g transform=\"translate(0,332) scale(0.1,-0.1)\" fill=\"\" stroke=\"none\"><path d=\"M2394 3279 l-29 -30 -3 -207 c-2 -182 0 -211 15 -242 39 -76 157 -76 196 0 15 31 17 60 15 243 l-3 209 -33 29 c-26 23 -41 29 -80 29 -41 0 -53 -5 -78 -31z\"\/><path d=\"M3085 3251 c-45 -19 -58 -50 -96 -229 -47 -217 -49 -260 -13 -295 52 -53 146 -42 177 20 16 31 87 366 87 410 0 70 -86 122 -155 94z\"\/><path d=\"M1751 3234 c-13 -9 -29 -31 -37 -50 -12 -29 -10 -49 21 -204 19 -94 39 -189 45 -210 14 -50 54 -80 110 -80 34 0 48 6 76 34 21 21 34 44 34 59 0 14 -18 113 -40 219 -37 178 -43 195 -70 221 -36 32 -101 37 -139 11z\"\/><path d=\"M1163 3073 c-36 -7 -73 -59 -73 -102 0 -56 133 -378 171 -413 34 -32 83 -37 129 -13 70 36 67 87 -16 290 -86 209 -89 214 -129 231 -35 14 -42 15 -82 7z\"\/><path d=\"M3689 3066 c-15 -9 -33 -30 -42 -48 -48 -103 -147 -355 -147 -375 0 -98 131 -148 192 -74 13 15 57 108 97 206 80 196 84 226 37 273 -30 30 -99 39 -137 18z\"\/><path d=\"M583 2784 c-38 -19 -67 -74 -58 -113 9 -42 211 -354 242 -373 16 -10 45 -18 66 -18 51 0 107 52 107 100 0 39 -1 41 -124 234 -80 126 -108 162 -133 173 -41 17 -61 16 -100 -3z\"\/><path d=\"M4250 2784 c-14 -9 -74 -91 -133 -183 -95 -150 -107 -173 -107 -213 0 -55 33 -94 87 -104 67 -13 90 8 211 198 130 202 137 225 78 284 -27 27 -42 34 -72 34 -22 0 -50 -8 -64 -16z\"\/><path d=\"M2275 2693 c-553 -48 -1095 -270 -1585 -649 -135 -104 -459 -423 -483 -476 -23 -49 -22 -139 2 -186 73 -142 361 -457 571 -626 285 -228 642 -407 990 -497 242 -63 336 -73 660 -74 310 0 370 5 595 52 535 111 1045 392 1455 803 122 121 250 273 275 326 19 41 19 137 0 174 -41 79 -309 363 -465 492 -447 370 -946 591 -1479 653 -113 14 -422 18 -536 8z m395 -428 c171 -34 330 -124 456 -258 112 -119 167 -219 211 -378 27 -96 24 -300 -5 -401 -72 -255 -236 -447 -474 -557 -132 -62 -201 -76 -368 -76 -167 0 -236 14 -368 76 -213 98 -373 271 -451 485 -162 444 86 934 547 1084 153 49 292 57 452 25z m909 -232 c222 -123 408 -262 593 -441 76 -74 138 -139 138 -144 0 -16 -233 -242 -330 -319 -155 -123 -309 -223 -461 -299 l-81 -41 32 46 c18 26 49 83 70 128 143 306 141 649 -6 957 -25 52 -61 116 -79 142 l-34 47 45 -20 c26 -10 76 -36 113 -56z m-2057 25 c-40 -58 -105 -190 -130 -263 -110 -324 -59 -707 132 -981 25 -35 42 -64 37 -64 -19 0 -241 119 -326 174 -188 122 -406 314 -532 468 l-58 71 108 103 c185 178 428 349 672 473 66 33 121 60 123 61 2 0 -10 -19 -26 -42z\"\/><path d=\"M2375 1950 c-198 -44 -350 -190 -395 -379 -18 -76 -8 -221 19 -290 114 -284 457 -406 731 -260 98 52 188 154 231 260 27 69 37 214 19 290 -38 163 -166 304 -326 360 -67 23 -215 33 -279 19z\"\/><\/g><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/dev95.site\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=\"0\" \/><\/p>\n<div class=\"pvc_clear\"><\/div>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"fp_fajr_begins":"","fp_fajr_iqamah":"","fp_dhuhr_begins":"","fp_dhuhr_iqamah":"","fp_asr_begins":"","fp_asr_iqamah":"","fp_maghrib_begins":"","fp_maghrib_iqamah":"","fp_isha_begins":"","fp_isha_iqamah":"","fp_midnight":"","fp_midnight_name":"","fp_sunrise":"","fp_single_prayer_begins_title":"","fp_single_prayer_iqamah_title":"","fp_prayer_times_for_today":"","fp_hijra_date":"","fp_fajr_name":"","fp_dhuhr_name":"","fp_asr_name":"","fp_maghrib_name":"","fp_isha_name":"","fp_sunrise_name":"","fp_currentDate":"","fp_current_time":"","fp_current_title":"","fp_current_location":"","fp_masjid_name":"","fp_prayer_title":"","fp_next_prayer_iqamah_time":"","fp_next_prayer_iqamah_title":"","fp_next_prayer_begins_time":"","fp_next_prayer_begins_title":"","fp_next_prayer_title":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[37],"tags":[],"class_list":["post-1468","post","type-post","status-publish","format-standard","hentry","category-posts"],"a3_pvc":{"activated":true,"total_views":0,"today_views":0},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How We Built The Deals Tab in Swift UI - Dev95<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/dev95.site\/ar\/how-we-built-the-deals-tab-in-swift-ui\/\" \/>\n<meta property=\"og:locale\" content=\"ar_AR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How We Built The Deals Tab in Swift UI - Dev95\" \/>\n<meta property=\"og:description\" content=\"Balancing Engineering Ambition with Product Realism Introduction In July of 2023, Etsy\u2019s App Updates team, responsible for the Updates feed in Etsy\u2019s mobile apps, set off with an ambitious goal: to revamp the Updates tab to become Deals, a homeRead more &gt;&gt;&gt;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/dev95.site\/ar\/how-we-built-the-deals-tab-in-swift-ui\/\" \/>\n<meta property=\"og:site_name\" content=\"Dev95\" \/>\n<meta property=\"article:published_time\" content=\"2024-03-05T22:22:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0\" \/>\n<meta name=\"author\" content=\"dev95\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u0643\u064f\u062a\u0628 \u0628\u0648\u0627\u0633\u0637\u0629\" \/>\n\t<meta name=\"twitter:data1\" content=\"dev95\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u0648\u0642\u062a \u0627\u0644\u0642\u0631\u0627\u0621\u0629 \u0627\u0644\u0645\u064f\u0642\u062f\u0651\u0631\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 \u062f\u0642\u064a\u0642\u0629\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/\"},\"author\":{\"name\":\"dev95\",\"@id\":\"https:\\\/\\\/dev95.site\\\/#\\\/schema\\\/person\\\/b807805ffe2916206b04d0938bce0298\"},\"headline\":\"How We Built The Deals Tab in Swift UI\",\"datePublished\":\"2024-03-05T22:22:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/\"},\"wordCount\":2637,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i.etsystatic.com\\\/inv\\\/cab771\\\/5865916855\\\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0\",\"articleSection\":[\"Posts\"],\"inLanguage\":\"ar\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/\",\"url\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/\",\"name\":\"How We Built The Deals Tab in Swift UI - Dev95\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i.etsystatic.com\\\/inv\\\/cab771\\\/5865916855\\\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0\",\"datePublished\":\"2024-03-05T22:22:54+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#breadcrumb\"},\"inLanguage\":\"ar\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"ar\",\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#primaryimage\",\"url\":\"https:\\\/\\\/i.etsystatic.com\\\/inv\\\/cab771\\\/5865916855\\\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0\",\"contentUrl\":\"https:\\\/\\\/i.etsystatic.com\\\/inv\\\/cab771\\\/5865916855\\\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/dev95.site\\\/how-we-built-the-deals-tab-in-swift-ui\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/dev95.site\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How We Built The Deals Tab in Swift UI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/dev95.site\\\/#website\",\"url\":\"https:\\\/\\\/dev95.site\\\/\",\"name\":\"Dev95\",\"description\":\"A comprehensive platform for data and knowledge, delivering reliable content that meets the aspirations of readers and enthusiasts.\",\"publisher\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/dev95.site\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"ar\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/dev95.site\\\/#organization\",\"name\":\"Dev95\",\"url\":\"https:\\\/\\\/dev95.site\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ar\",\"@id\":\"https:\\\/\\\/dev95.site\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/dev95.site\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/rbrrbr-6.png?fit=512%2C512&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/dev95.site\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/rbrrbr-6.png?fit=512%2C512&ssl=1\",\"width\":512,\"height\":512,\"caption\":\"Dev95\"},\"image\":{\"@id\":\"https:\\\/\\\/dev95.site\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/dev95.site\\\/#\\\/schema\\\/person\\\/b807805ffe2916206b04d0938bce0298\",\"name\":\"dev95\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ar\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a70a73d950838b20cd80d7ebdc955737e802e8cd896044c5473b32b946c0662a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a70a73d950838b20cd80d7ebdc955737e802e8cd896044c5473b32b946c0662a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a70a73d950838b20cd80d7ebdc955737e802e8cd896044c5473b32b946c0662a?s=96&d=mm&r=g\",\"caption\":\"dev95\"},\"url\":\"https:\\\/\\\/dev95.site\\\/ar\\\/author\\\/mohammad\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How We Built The Deals Tab in Swift UI - Dev95","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/dev95.site\/ar\/how-we-built-the-deals-tab-in-swift-ui\/","og_locale":"ar_AR","og_type":"article","og_title":"How We Built The Deals Tab in Swift UI - Dev95","og_description":"Balancing Engineering Ambition with Product Realism Introduction In July of 2023, Etsy\u2019s App Updates team, responsible for the Updates feed in Etsy\u2019s mobile apps, set off with an ambitious goal: to revamp the Updates tab to become Deals, a homeRead more &gt;&gt;&gt;","og_url":"https:\/\/dev95.site\/ar\/how-we-built-the-deals-tab-in-swift-ui\/","og_site_name":"Dev95","article_published_time":"2024-03-05T22:22:54+00:00","og_image":[{"url":"https:\/\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0","type":"","width":"","height":""}],"author":"dev95","twitter_card":"summary_large_image","twitter_misc":{"\u0643\u064f\u062a\u0628 \u0628\u0648\u0627\u0633\u0637\u0629":"dev95","\u0648\u0642\u062a \u0627\u0644\u0642\u0631\u0627\u0621\u0629 \u0627\u0644\u0645\u064f\u0642\u062f\u0651\u0631":"14 \u062f\u0642\u064a\u0642\u0629"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#article","isPartOf":{"@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/"},"author":{"name":"dev95","@id":"https:\/\/dev95.site\/#\/schema\/person\/b807805ffe2916206b04d0938bce0298"},"headline":"How We Built The Deals Tab in Swift UI","datePublished":"2024-03-05T22:22:54+00:00","mainEntityOfPage":{"@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/"},"wordCount":2637,"commentCount":0,"publisher":{"@id":"https:\/\/dev95.site\/#organization"},"image":{"@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#primaryimage"},"thumbnailUrl":"https:\/\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0","articleSection":["Posts"],"inLanguage":"ar","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/","url":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/","name":"How We Built The Deals Tab in Swift UI - Dev95","isPartOf":{"@id":"https:\/\/dev95.site\/#website"},"primaryImageOfPage":{"@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#primaryimage"},"image":{"@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#primaryimage"},"thumbnailUrl":"https:\/\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0","datePublished":"2024-03-05T22:22:54+00:00","breadcrumb":{"@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#breadcrumb"},"inLanguage":"ar","potentialAction":[{"@type":"ReadAction","target":["https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/"]}]},{"@type":"ImageObject","inLanguage":"ar","@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#primaryimage","url":"https:\/\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0","contentUrl":"https:\/\/i.etsystatic.com\/inv\/cab771\/5865916855\/inv_fullxfull.5865916855_49mug0jy.jpg?version=0"},{"@type":"BreadcrumbList","@id":"https:\/\/dev95.site\/how-we-built-the-deals-tab-in-swift-ui\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/dev95.site\/"},{"@type":"ListItem","position":2,"name":"How We Built The Deals Tab in Swift UI"}]},{"@type":"WebSite","@id":"https:\/\/dev95.site\/#website","url":"https:\/\/dev95.site\/","name":"Dev95","description":"A comprehensive platform for data and knowledge, delivering reliable content that meets the aspirations of readers and enthusiasts.","publisher":{"@id":"https:\/\/dev95.site\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/dev95.site\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"ar"},{"@type":"Organization","@id":"https:\/\/dev95.site\/#organization","name":"Dev95","url":"https:\/\/dev95.site\/","logo":{"@type":"ImageObject","inLanguage":"ar","@id":"https:\/\/dev95.site\/#\/schema\/logo\/image\/","url":"https:\/\/i0.wp.com\/dev95.site\/wp-content\/uploads\/2026\/07\/rbrrbr-6.png?fit=512%2C512&ssl=1","contentUrl":"https:\/\/i0.wp.com\/dev95.site\/wp-content\/uploads\/2026\/07\/rbrrbr-6.png?fit=512%2C512&ssl=1","width":512,"height":512,"caption":"Dev95"},"image":{"@id":"https:\/\/dev95.site\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/dev95.site\/#\/schema\/person\/b807805ffe2916206b04d0938bce0298","name":"dev95","image":{"@type":"ImageObject","inLanguage":"ar","@id":"https:\/\/secure.gravatar.com\/avatar\/a70a73d950838b20cd80d7ebdc955737e802e8cd896044c5473b32b946c0662a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/a70a73d950838b20cd80d7ebdc955737e802e8cd896044c5473b32b946c0662a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a70a73d950838b20cd80d7ebdc955737e802e8cd896044c5473b32b946c0662a?s=96&d=mm&r=g","caption":"dev95"},"url":"https:\/\/dev95.site\/ar\/author\/mohammad\/"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/posts\/1468","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/comments?post=1468"}],"version-history":[{"count":0,"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/posts\/1468\/revisions"}],"wp:attachment":[{"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/media?parent=1468"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/categories?post=1468"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev95.site\/ar\/wp-json\/wp\/v2\/tags?post=1468"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}